【问题标题】:Jackson Serialize Instant to Nanosecond Issue杰克逊序列化即时到纳秒问题
【发布时间】:2019-10-14 03:48:48
【问题描述】:

Jackson 序列化java.time.InstantWRITE_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 json serialization jackson java-time


【解决方案1】:

当我们使用Java 8 Time 包和Jackson 时,最好使用jackson-modules-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}

如果您想在所有情况下都消除所有零,请编写您自己在 ShortInstantSerializer 类中声明的 getNanoseconds 函数。

【讨论】:

  • 我不能覆盖 withFormat 方法,因为它说 JSR310FormattedSerializerBase 类不是公共的,不能在包外使用。
  • @oxyt,注意ShortInstantSerializer 类是在com.fasterxml.jackson.datatype.jsr310.ser 包中创建的。在您的源文件夹中创建该包并将此类移到那里。它应该在那之后工作。
  • 如果 getNanoseconds.applyAsInt(value) 小于 3 位数字,String concatenated = getEpochSeconds.applyAsLong(value) + "." + getNanoseconds.applyAsInt(value); 不会返回正确的字符串。你必须在左边用 0 填充它。所以如果你有15813855522 你会得到1581385552.2 而不是1581385552.002
  • @PawelZieminski,感谢您指出这一点。我修好了它。我从Pad a String with Zeros or Spaces in Java得到的一种填充方法。
【解决方案2】:

我采用了上面 Michal 的想法,并将现有的 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),需要更改setScale(3)得到的BigDecimal
猜你喜欢
  • 2020-12-24
  • 1970-01-01
  • 2014-12-18
  • 2016-04-18
  • 1970-01-01
  • 2018-01-14
  • 2019-10-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多