问题描述
使用 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,如下:
@Configuration
class LocalDateTimeSerializerConfig {
@Value("\${spring.jackson.date-time-format:yyyy-MM-dd HH:mm:ss}")
private val datetimepattern: String? = null
fun localDateTimeSerializer(): LocalDateTimeSerializer {
return LocalDateTimeSerializer(DateTimeFormatter.ofPattern(datetimepattern))
}
fun localDateTimeDeserializer():LocalDateTimeDeserializer{
return LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(datetimepattern))
}
@Bean
fun jackson2ObjectMapperBuilderCustomizer(): Jackson2ObjectMapperBuilderCustomizer {
return Jackson2ObjectMapperBuilderCustomizer { builder: Jackson2ObjectMapperBuilder ->
builder.serializerByType(LocalDateTime::class.java, localDateTimeSerializer())
builder.deserializerByType(LocalDateTime::class.java,localDateTimeDeserializer())
}
}
}
这样就可以了,后面的配置就是 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 装载的方法:
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder(ApplicationContext applicationContext,
List<Jackson2ObjectMapperBuilderCustomizer> customizers) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.applicationContext(applicationContext);
customize(builder, customizers);
return builder;
}
我们再看需要的 参数 List<Jackson2ObjectMapperBuilderCustomizer.>,换句话说,Jackson2ObjectMapperBuilderCustomizer
就是给我们留的口子,我们实现它来达到自定义配置的目的。
相关参考:
- JSR310 标准
- JSRs: Java Specification Requests/JSR 310: Date and Time API