Jackson , java.time , ISO 8601 , 无毫秒序列化

Posted

技术标签:

【中文标题】Jackson , java.time , ISO 8601 , 无毫秒序列化【英文标题】:Jackson , java.time , ISO 8601 , serialize without milliseconds 【发布时间】:2017-03-22 02:57:33 【问题描述】:

我使用的是 Jackson 2.8,需要与一个不允许在 ISO 8601 时间戳内以毫秒为单位的 API 进行通信。

预期的格式是这样的:"2016-12-24T00:00:00Z"

我正在使用 Jackson 的 JavaTimeModule,并将 WRITE_DATES_AS_TIMESTAMPS 设置为 false

但这将打印毫秒。

所以我尝试使用 objectMapper.setDateFormat 并没有改变任何东西。

我目前的解决方法是这样的:

ObjectMapper om = new ObjectMapper();

DateTimeFormatter dtf = new DateTimeFormatterBuilder()
    .appendInstant(0)
    .toFormatter();

JavaTimeModule jtm = new JavaTimeModule();
jtm.addSerializer(Instant.class, new JsonSerializer<Instant>() 
    @Override
    public void serialize(Instant value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException 
        gen.writeString(dtf.format(value));
    
);

om.registerModule(jtm);

我正在覆盖 Instant.class 的默认序列化程序,它可以工作。


有什么好的方法可以使用一些配置参数来解决这个问题吗?

【问题讨论】:

你试过DateTimeFormatter.ISO_INSTANT作为一种格式吗? docs.oracle.com/javase/8/docs/api/java/time/format/… 目标 API 不受您的控制? 是的,试过了。它还将打印毫秒。如果我明确创建一个Instant 并将毫秒设置为0,也许它会起作用。但这对我的用例来说还不够安全。它必须始终忽略/丢弃毫秒以进行序列化。 ... EDIT: 是的,API 来自某个外部公司。没有控制权。 这几乎是相反但相同的主题:***.com/questions/45276807/… 【参考方案1】:

更新:

只需在Instant 属性上方添加带有日期格式的@JsonFormat 注释。这很容易。

如果您有一个带有 JavaTimeModule 的 ObjectMapper,如下所示:

ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());

如果您有一个具有Instant 属性的类,您应该添加@JsonFormat 注释并放置没有毫秒的日期模式。就像下一个:

public static class TestDate 

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd hh:mm:ss", timezone = "UTC")
    Instant instant;

    //getters & setters

因此,如果您将一个对象序列化为 Json,它将完美地工作:

String json = mapper.writeValueAsString(testDate);
System.out.println(json); 

输出

“即时”:“2016-11-10 06:03:06”


旧答案。我不知道为什么,但它不能正常工作:

您可以使用Jackson2ObjectMapperBuilder 来构建它。

您只需要添加所需的 dateFormat 即可。就像下一个:

DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");

ObjectMapper mapper = Jackson2ObjectMapperBuilder
            .json()       
            .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) 
            .modules(new JavaTimeModule())
            .dateFormat(dateFormat)
            .build();

【讨论】:

复制并粘贴您的代码。它不起作用。仍然得到日期+时间+毫秒。 :( 谢谢。但是这样我必须注释所有和每个 Instant 字段。我想在 ObjectMapper 中全局设置该格式。 对于 24 小时格式,它应该是“yyyy-MM-dd HH:mm:ss”以避免混淆(上午和下午不是此输出的一部分)【参考方案2】:

这是您可以全局设置的替代方法,但需要您将 ZonedDateTime 与即时格式化程序一起使用,因为我们无法为 Java 时间模块提供的 Instant 串行器设置格式。

您不会立即看到使用分区日期时间的任何副作用,因为杰克逊会单独序列化区域 ID,并且默认情况下是禁用的。所以从技术上讲,这类似于将格式化程序应用于Instant

以这种方式使用时,ZonedDateTime 序列化程序将序列化委托给InstantBaseSerializer 并使用指定的自定义格式。

@RunWith(JUnit4.class)
public class InstantNoMillisTest 

    private ObjectMapper objectMapper;

    @Before
    public void init() 
        JavaTimeModule module = new JavaTimeModule();
        ZonedDateTimeSerializer zonedDateTimeSerializer = new ZonedDateTimeSerializer(new DateTimeFormatterBuilder().appendInstant(0).toFormatter());
        module.addSerializer(ZonedDateTime.class, zonedDateTimeSerializer);
        module.addDeserializer(ZonedDateTime.class, InstantDeserializer.ZONED_DATE_TIME);

        objectMapper = Jackson2ObjectMapperBuilder.json()
                .modules(module)
                .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
                .build();
    

    @Test
    public void serialize() throws IOException 
        ZonedDateTime zonedDateTime = ZonedDateTime.now();
        String noMillis = objectMapper.writeValueAsString(zonedDateTime);
        System.out.print(noMillis);
    

    @Test
    public void deserialize() throws IOException 
        String dateTime = "\"2017-10-26T12:54:59Z\"";
        ZonedDateTime noMillis = objectMapper.readValue(dateTime, ZonedDateTime.class);
        System.out.print(noMillis);
    

【讨论】:

这使得反序列化变得非常困难,因为没有类似的方法可以反向进行破解 @Whimusical 更新了答案以包含反序列化测试用例。 instantdeserializer 在这里工作吗?真的很有趣【参考方案3】:

这里有一些格式化Instant字段的Kotlin代码,所以它们不会包含毫秒,你可以使用自定义日期格式化器

ObjectMapper().apply 
        val javaTimeModule = JavaTimeModule()
        javaTimeModule.addSerializer(Instant::class.java, Iso8601WithoutMillisInstantSerializer())
        registerModule(javaTimeModule)
        disable(WRITE_DATES_AS_TIMESTAMPS)
    

private class Iso8601WithoutMillisInstantSerializer
        : InstantSerializer(InstantSerializer.INSTANCE, false, DateTimeFormatterBuilder().appendInstant(0).toFormatter())

【讨论】:

优秀的答案。

以上是关于Jackson , java.time , ISO 8601 , 无毫秒序列化的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot 问题使用 Jackson 序列化 java.time.LocalDateTime 以返回 ISO-8601 JSON 时间戳?

Jackson 将 ISO8601 格式的日期时间反序列化为 Java8 Instant

无法使用 Jackson 将 java.time.LocalDate 序列化为字符串

com.fasterxml.jackson.databind.JsonMappingException:java.util.Optional 不能转换为 java.time.LocalDate

com.fasterxml.jackson.databind.exc.InvalidDefinitionException:无法构造`java.time.Instant`的实例(没有创建者,如默认构造

使用 Spring Boot 2 和 Kotlin 进行 Jackson 反序列化,无法构造 `java.time.LocalDate` 的实例