Джексон сериализует мгновенный выпуск в наносекунду

Jackson сериализует java.time.Instant с включенным WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS по умолчанию.

Он производит JSON вот так

{ "timestamp":1421261297.356000000 }

Интересно, есть ли способ избавиться от нулей в конце. Я хочу что-то вроде:

{ "timestamp":1421261297.356 }

Я старался:

mapper.configure( SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS, false );
mapper.configure( SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, true );

Но эта конфигурация изменяет его на миллисекундное представление 1421261297356. Мне нужна часть секунд и дробная часть миллисекунд.

Написать свой собственный (де-)сериализатор?

assylias 28.05.2019 18:36

Что не так с отметкой времени (свойства) в миллисекундах?

Antoniossss 28.05.2019 22:02
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
5
2
6 221
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Когда мы работаем с пакетом Java 8Time и Jackson, хорошей идеей будет использовать проект Джексон-модули-java8, который обслуживает множество сериализаторов и десериализаторов, готовых к использованию. Чтобы включить его, нам нужно зарегистрировать модуль JavaTimeModule. Для сериализации используется InstantInstantSerializer. Когда мы проверим, как это реализовано, мы обнаружим, что за кулисами используется метод DecimalUtils.toDecimal. Похоже, что в конце значения наносекунд всегда добавляются нули.

Мы можем написать наш InstantSerializer, который сериализует его желаемым образом. Поскольку классы в этом проекте не готовы к легкому расширению, нам нужно реализовать множество нежелательных методов и конструкторов. Также нам нужно создать в нашем проекте пакет com.fasterxml.jackson.datatype.jsr310.ser и создать там нашу реализацию. См. пример ниже:

package com.fasterxml.jackson.datatype.jsr310.ser;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;

import java.io.IOException;
import java.math.BigDecimal;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.function.ToIntFunction;
import java.util.function.ToLongFunction;

public class ShortInstantSerializer extends InstantSerializerBase<Instant> {

    private ToLongFunction<Instant> getEpochSeconds = Instant::getEpochSecond;
    private ToIntFunction<Instant> getNanoseconds = i -> i.getNano() / 1_000_000;

    public ShortInstantSerializer() {
        super(Instant.class, Instant::toEpochMilli, Instant::getEpochSecond, Instant::getNano, null);
    }

    protected ShortInstantSerializer(ShortInstantSerializer base, Boolean useTimestamp, Boolean useNanoseconds, DateTimeFormatter formatter) {
        super(base, useTimestamp, useNanoseconds, formatter);
    }

    @Override
    protected JSR310FormattedSerializerBase<?> withFormat(Boolean useTimestamp, DateTimeFormatter formatter, JsonFormat.Shape shape) {
        return new ShortInstantSerializer(this, useTimestamp, null, formatter);
    }

    @Override
    public void serialize(Instant value, JsonGenerator generator, SerializerProvider provider) throws IOException {
        if (useTimestamp(provider)) {
            if (useNanoseconds(provider)) {
                generator.writeNumber(new BigDecimal(toShortVersion(value)));
                return;
            }
        }

        super.serialize(value, generator, provider);
    }

    private String toShortVersion(final Instant value) {
        return getEpochSeconds.applyAsLong(value) + "." + padWithZeros(getNanoseconds.applyAsInt(value));
    }

    private String padWithZeros(final int value) {
        return String.format("%1$3s", String.valueOf(value)).replace(' ', '0');
    }
}

И пример, как его использовать:

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.ser.ShortInstantSerializer;

import java.time.Instant;

public class JsonApp {

    public static void main(String[] args) throws Exception {
        JavaTimeModule javaTimeModule = new JavaTimeModule();
        javaTimeModule.addSerializer(Instant.class, new ShortInstantSerializer());

        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(javaTimeModule);
        mapper.disable(SerializationFeature.INDENT_OUTPUT);

        System.out.println(mapper.writeValueAsString(new Element()));
    }
}

class Element {

    private Instant timestamp = Instant.now();

    public Instant getTimestamp() {
        return timestamp;
    }

    public void setTimestamp(Instant timestamp) {
        this.timestamp = timestamp;
    }
}

Над кодом печатается:

{"timestamp":1559074287.223}

Если вы хотите избавиться от всех нулей во всех случаях, напишите свою собственную функцию getNanoseconds, объявленную в классе ShortInstantSerializer.

Я не могу переопределить метод withFormat, потому что он говорит, что класс JSR310FormattedSerializerBase не является общедоступным и не может использоваться вне пакета.

oxyt 11.12.2019 10:54

@oxyt, обратите внимание, что класс ShortInstantSerializer создан в пакете com.fasterxml.jackson.datatype.jsr310.ser. В исходной папке создайте этот пакет и переместите туда этот класс. После этого должно работать.

Michał Ziober 11.12.2019 11:45
String concatenated = getEpochSeconds.applyAsLong(value) + "." + getNanoseconds.applyAsInt(value); не возвращает правильную строку, если getNanoseconds.applyAsInt(value) меньше трехзначного числа. Вы должны дополнить его 0 слева. Итак, если у вас есть 1581385552 и 2, вы получите 1581385552.2 вместо 1581385552.002.
Pawel Zieminski 07.05.2020 23:14

@PawelZieminski, спасибо, что указали мне на это. Я починил это. Один из методов заполнения, который я получил от Заполните строку нулями или пробелами в Java.

Michał Ziober 08.05.2020 00:11

Я взял идею Михала выше и завернул существующий com.fasterxml.jackson.datatype.jsr310.DecimalUtils#toBigDecimal(long seconds, int nanoseconds) в сериализатор.

class ShortInstantSerializer extends StdSerializer<Instant> {
    ShortInstantSerializer() {
        super(Instant.class);
    }

    @Override
    public void serialize(Instant value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        gen.writeNumber(DecimalUtils.toBigDecimal(value.getEpochSecond(), value.getNano()));
    }
}

Кроме того, если вы собираетесь в какой-то момент прочитать его, значение будет десериализовано в Double. Чтобы вернуться, сделайте это (спасибо, inverwebs!)

public static Instant toInstant(Double d) {
    long seconds = d.longValue();
    long micros = Math.round((d - seconds) * 1_000_000);
    return Instant.ofEpochSecond(seconds).plus(micros , ChronoUnit.MICROS);
}

Чтобы действительно получить короткое десятичное число в наносекундах (без дополнительного 0 в конце), вам нужно изменить BigDecimal, полученное с помощью setScale(3)

Patrick 10.07.2021 13:00

Другие вопросы по теме