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
. Мне нужна часть секунд и дробная часть миллисекунд.
Что не так с отметкой времени (свойства) в миллисекундах?
Когда мы работаем с пакетом Java 8
Time
и Jackson
, хорошей идеей будет использовать проект Джексон-модули-java8, который обслуживает множество сериализаторов и десериализаторов, готовых к использованию. Чтобы включить его, нам нужно зарегистрировать модуль JavaTimeModule
. Для сериализации используется Instant
InstantSerializer. Когда мы проверим, как это реализовано, мы обнаружим, что за кулисами используется метод 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, обратите внимание, что класс ShortInstantSerializer
создан в пакете com.fasterxml.jackson.datatype.jsr310.ser
. В исходной папке создайте этот пакет и переместите туда этот класс. После этого должно работать.
String concatenated = getEpochSeconds.applyAsLong(value) + "." + getNanoseconds.applyAsInt(value);
не возвращает правильную строку, если getNanoseconds.applyAsInt(value)
меньше трехзначного числа. Вы должны дополнить его 0 слева. Итак, если у вас есть 1581385552
и 2
, вы получите 1581385552.2
вместо 1581385552.002
.
@PawelZieminski, спасибо, что указали мне на это. Я починил это. Один из методов заполнения, который я получил от Заполните строку нулями или пробелами в Java.
Я взял идею Михала выше и завернул существующий 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)
Написать свой собственный (де-)сериализатор?