为什么在使用Jackson的ObjectMapper和Java 8日期时,使用dateFormat时毫秒没有被排除在JSON之外?

5

我正在尝试解决为什么Jackson(2.9.5)无法正确地格式化Java 8日期的问题。

data class Test(
        val zonedDateTim: ZonedDateTime = ZonedDateTime.now(),
        val offsetDateTim: OffsetDateTime = OffsetDateTime.now(),
        val date: Date = Date(),
        val localDateTime: LocalDateTime = LocalDateTime.now()
)

val mapper = ObjectMapper().apply {
    registerModule(KotlinModule())
    registerModule(JavaTimeModule())
    dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss")
    enable(SerializationFeature.INDENT_OUTPUT)
}

println(mapper.writeValueAsString(Test()))

根据我提供的日期格式,我希望得到没有毫秒的日期格式,但实际结果如下:

{
  "zonedDateTim" : "2018-07-27T13:18:26.452+02:00",
  "offsetDateTim" : "2018-07-27T13:18:26.452+02:00",
  "date" : "2018-07-27T13:18:26",
  "localDateTime" : "2018-07-27T13:18:26.452"
}

为什么格式化的日期中包含毫秒?
2个回答

8

dateFormat仅适用于Date对象 - 另外三个对象由JavaTimeModule处理,该模块默认使用ISO格式。

如果您需要不同的格式,可以使用以下方式:

val format = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss");

val javaTimeModule = JavaTimeModule();
javaTimeModule.addSerializer(LocalDateTime.class, LocalDateTimeSerializer(format));
javaTimeModule.addSerializer(ZonedDateTime.class, ZonedDateTimeSerializer(format));
mapper.registerModule(javaTimeModule);

你可能还需要添加mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);,但我不确定是否必要。
此外,请注意,使用该格式将丢失时区和偏移信息。因此,你可能需要为偏移和带时区的日期时间选择不同的格式。

1
你自己测试过吗?实现你的更改对我得到的输出没有任何影响。最重要的是,我觉得配置LocalDateTimeDeserializer不应该对ZonedDateTime和OffsetDateTime产生任何影响。 - Daniel
1
@Daniel 对不起,应该是 javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(format));。请看我的修改后的答案。 - assylias
感谢您的更新答案。我已经接受它作为正确答案。对于未来的读者,您可能需要再次更新您的答案,因为OffsetDateTimeSerializer中没有以格式为参数的公共ctor。 - Daniel
@Daniel确实 - 你是怎么解决这个问题的?你写了一个自定义序列化器吗? - assylias
1
我目前还没有做过,但这将是我采取的方法。由于这更像是一个副业项目,我还没有时间去处理它。 - Daniel
显示剩余2条评论

2

assylias的回答是正确的。以下是一些进一步的想法。

截断

如果你真的不想要小数秒,可以将其截断为整秒。

Instant instant = Instant.now().truncatedTo( ChronoUnit.SECONDS ) ;
OffsetDateTime odt = OffsetDateTime.now().truncatedTo( ChronoUnit.SECONDS ) ;
ZonedDateTime zdt = ZonedDateTime.now().truncatedTo( ChronoUnit.SECONDS ) ;

各种toString方法使用的格式化程序默认情况下会省略任何代表小数秒的文本,如果该值为零。

因此,以下值的结果为:

2018-07-27T13:18:26.452+02:00

"...变成了:"

2018-07-27T13:18:26.000+02:00

"...并且它的字符串表示将以秒为单位生成而没有小数部分:"

2018-07-27T13:18:26+02:00

"试一下吧。"
OffsetDateTime odt = OffsetDateTime.parse( "2018-07-27T13:18:26.452+02:00" ) ;
OffsetDateTime odtTrunc = odt.truncatedTo( ChronoUnit.SECONDS ) ;

System.out.println( "odt.toString(): " + odt ) ;
System.out.println( "odtTrunc.toString(): " + odtTrunc ) ;

中译英:

尝试在IdeOne.com上实时运行该代码

odt.toString():2018-07-27T13:18:26.452+02:00

odtTrunc.toString():2018-07-27T13:18:26+02:00

避免使用旧类

您问题中的代码让我感到困惑。不要将麻烦的旧版日期时间类(例如DateSimpleDateFormat)与现代的java.time类混合使用。现代类已经完全取代了旧版类。

时间线

请明确LocalDateTime的作用与InstantOffsetDateTimeZonedDateTime等有很大区别。 LocalDateTime对象故意缺乏任何时区或UTC偏移量的概念。因此,它不能表示一个时刻,不是时间线上的一个点。


关于 java.time

java.time 框架内置于 Java 8 及更高版本。这些类取代了旧的 传统 日期时间类,如 java.util.DateCalendarSimpleDateFormat

Joda-Time项目现在处于维护模式,建议迁移到java.time类。

想要了解更多,请参阅Oracle教程。并在Stack Overflow上搜索许多示例和解释。规范是JSR 310

您可以直接与数据库交换java.time对象。使用符合JDBC 4.2或更高版本的JDBC驱动程序。无需字符串,也无需java.sql.*类。

如何获取java.time类?

ThreeTen-Extra项目扩展了java.time的附加类。该项目是java.time可能未来添加的试验场。您可以在此处找到一些有用的类,例如Interval, YearWeek, YearQuarter, 以及更多

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接