Jackson 序列化:Cannot deserialize value of type `java.time.LocalDateTime`

水深无声 2024-03-27 17:08 208阅读 0赞

问题描述

使用 jackson 反序列化异常如下:

Caused by: com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type java.time.LocalDateTime from String “2023-02-13 19:43:01”: Failed to deserialize java.time.LocalDateTime: (java.time.format.DateTimeParseException) Text ‘2023-02-13 19:43:01’ could not be parsed at index 10
at [Source: (String)“{“code”:0,“message”:“OK”,“request_id”:“20230214155816454DFD3C7EAE510F39CE”,“data”:{“list”:[{“creative_create_time”:“2023-02-13 19:43:01”,“creative_id”:1757715969881150,“creative_modify_time”:“2023-02-13 19:43:02”}],“page_info”:{“page”:1,“page_size”:1,“total_number”:411,“total_page”:411}}}”; line: 1, column: 116] (through reference chain: com.xxx.core.domain.vo.AdResultVO[“data”]->com.xxx.core.domain.vo.AdDataVO[“list”]->java.util.ArrayList[0]->com.xxx.core.domain.vo.AdCreativityDetailsVO[“creative_create_time”])

LocalDateTime 类型的 creative_modify_time 字段反序列化失败,一看到日期字段序列化你可能就头疼了!大概率是 Jackson 配置上的问题。


原因分析:

项目使用 Springboot 技术框架,并使用 Jackson 做序列化工具。

那这里导致问题的原因是因为通常时间序列化成字符串的时候都是 yyyy-MM-dd HH:mm:ss

但是 Jackson 默认的序列化格式是国际化的时间标准格式:yyyy-MM-ddTHH:mm:ss,区别就在于中间多了个 T

我们找找源头,使用了 LocalTimeDeserializer 反序列化器:

在这里插入图片描述

我们继续看看实际的格式:

在这里插入图片描述

综上,对于这种 2023-02-13 19:43:01 字符串想要反序列化成 LocalDateTime 类型,需要我们自定义我们需要的 DateTimeFormatter


解决方案:

在 Spring 体系下,已经对 Jackson 做了很好的一层包装,也预留了口子,让我们能够很轻易的自定义序列化格式。

我们要做的就是在 Jackson2ObjectMapperBuilderCustomizer 中自定义配置,然后将其装配为 Bean,如下:

  1. @Configuration
  2. class LocalDateTimeSerializerConfig {
  3. @Value("\${spring.jackson.date-time-format:yyyy-MM-dd HH:mm:ss}")
  4. private val datetimepattern: String? = null
  5. fun localDateTimeSerializer(): LocalDateTimeSerializer {
  6. return LocalDateTimeSerializer(DateTimeFormatter.ofPattern(datetimepattern))
  7. }
  8. fun localDateTimeDeserializer():LocalDateTimeDeserializer{
  9. return LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(datetimepattern))
  10. }
  11. @Bean
  12. fun jackson2ObjectMapperBuilderCustomizer(): Jackson2ObjectMapperBuilderCustomizer {
  13. return Jackson2ObjectMapperBuilderCustomizer {
  14. builder: Jackson2ObjectMapperBuilder ->
  15. builder.serializerByType(LocalDateTime::class.java, localDateTimeSerializer())
  16. builder.deserializerByType(LocalDateTime::class.java,localDateTimeDeserializer())
  17. }
  18. }
  19. }

这样就可以了,后面的配置就是 SpringBoot 自动将配置装配到我们的 ObjectMapper 对象中。

当然,依葫芦画瓢,你还可以自定义 LocalDate、LocalTime … 等自定义序列化配置 …

原理探索:

日期时间:

JSR310 规定了「日期时间」处理的新标准,并在 jdk1.8 的版本中进行了实现,其中你熟悉的 LocalDateTime、LocalDate 等就是 JSR310 标准的实现类。

Jackson 工具在进行「日期时间」序列化/反序列化的时候也采用 jdk1.8 中 JSR310 标准实现来处理。

不过 Jackson 单独罗列一个模块来粘合「Jackson 和 jdk1.8 的 JSR310 实现」,这个模块名就叫做 jackson-datatype-jsr310,你的项目里应该能看到这个包的引入。

Jackson 在全世界范围内流行,Spring 也将其作为默认的序列化框架,来对请求中的参数做 序列化和反序列化 行为。

当然,实际情况下,你应该也经常使用 Jackson, 尤其是 日期时间 类参数有着特殊的序列化需求,大部分工作 Spring 都帮你做了,你只需要添加你的个性化序列化方式即可(参数配置、Bean 实例等)

Jackson:

你应该也猜到了,Spring 这个中间者,会帮你初始化 ObjectMapper 实例,同时还会预留一个口子,方便你自定义配置。

其中关键的类是 Jackson2ObjectMapperBuilder,从名字应该也看出来了,该类专门用于构建 ObjectMapper。

熟悉 SpringBoot 的你应该知道,约定大于配置,所以,我们继续找到 自动装载 Bean 的类:JacksonAutoConfiguration

注意到其中 Jackson2ObjectMapperBuilder 装载的方法:

  1. @Bean
  2. @Scope("prototype")
  3. @ConditionalOnMissingBean
  4. Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder(ApplicationContext applicationContext,
  5. List<Jackson2ObjectMapperBuilderCustomizer> customizers) {
  6. Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
  7. builder.applicationContext(applicationContext);
  8. customize(builder, customizers);
  9. return builder;
  10. }

我们再看需要的 参数 List,换句话说,Jackson2ObjectMapperBuilderCustomizer 就是给我们留的口子,我们实现它来达到自定义配置的目的。

相关参考:

  1. JSR310 标准
  2. JSRs: Java Specification Requests/JSR 310: Date and Time API

发表评论

表情:
评论列表 (有 0 条评论,208人围观)

还没有评论,来说两句吧...

相关阅读