ZonedDateTime无法从JSON反序列化,但测试格式化可正常解析
问题描述
通过JSON传入日期字符串"29-OCT-21 12.00.00.000000000 AM UTC",希望将其反序列化为ZonedDateTime类型,实体类配置如下:
@JsonProperty("createdTs") @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MMM-yy hh.mm.ss.SSSSSSSSS a z", with = JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) private ZonedDateTime createdTs;
但出现反序列化错误:
org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `java.time.ZonedDateTime` from String "29-OCT-21 12.00.00.000000000 AM UTC": Failed to deserialize java.time.ZonedDateTime: (java.time.format.DateTimeParseException) Text '29-OCT-21 12.00.00.000000000 AM UTC' could not be parsed
而相同格式的日期字符串,在测试用例中使用DateTimeFormatterBuilder手动解析可以正常运行:
DateTimeFormatter formatter = new DateTimeFormatterBuilder().parseCaseInsensitive().appendPattern("dd-MMM-yy hh.mm.ss.SSSSSSSSS a z").toFormatter(Locale.getDefault()); locationPayload.setcreatedTs(ZonedDateTime.parse("29-OCT-21 12.00.00.000000000 AM UTC", formatter));
问题原因
- Locale不匹配:手动解析时指定了
Locale.getDefault(),但Jackson默认使用的Locale可能与测试环境不一致,导致月份缩写(OCT)无法被正确识别。 - 大小写不敏感配置无效:
JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES仅用于忽略JSON属性名的大小写,而非日期字符串内容的大小写,因此日期中的大写月份缩写无法被Jackson默认解析器识别。 - 纳秒解析兼容性:Jackson默认的
ZonedDateTime解析器对9位纳秒的处理逻辑,与手动构建的DateTimeFormatter存在差异。
解决方案
方案1:自定义Jackson日期反序列化器
在实体类字段上指定自定义反序列化器,直接复用测试用例中验证过的格式化逻辑:
@JsonProperty("createdTs") @JsonDeserialize(using = CustomZonedDateTimeDeserializer.class) private ZonedDateTime createdTs;
实现自定义反序列化器:
public class CustomZonedDateTimeDeserializer extends JsonDeserializer<ZonedDateTime> { private static final DateTimeFormatter FORMATTER = new DateTimeFormatterBuilder() .parseCaseInsensitive() .appendPattern("dd-MMM-yy hh.mm.ss.SSSSSSSSS a z") .toFormatter(Locale.getDefault()); @Override public ZonedDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { String dateStr = p.getText(); return ZonedDateTime.parse(dateStr, FORMATTER); } }
方案2:全局配置Jackson的日期格式化规则
通过配置类全局替换Jackson的ZonedDateTime解析逻辑,避免每个字段单独配置:
@Configuration public class JacksonConfig { @Bean public ObjectMapper objectMapper() { ObjectMapper objectMapper = new ObjectMapper(); JavaTimeModule javaTimeModule = new JavaTimeModule(); DateTimeFormatter formatter = new DateTimeFormatterBuilder() .parseCaseInsensitive() .appendPattern("dd-MMM-yy hh.mm.ss.SSSSSSSSS a z") .toFormatter(Locale.getDefault()); javaTimeModule.addDeserializer(ZonedDateTime.class, new JsonDeserializer<ZonedDateTime>() { @Override public ZonedDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { return ZonedDateTime.parse(p.getText(), formatter); } }); objectMapper.registerModule(javaTimeModule); return objectMapper; } }
此时实体类的@JsonFormat注解可以保留,也可以去掉,因为全局序列化器已经覆盖了默认逻辑。
方案3:修正@JsonFormat配置
仅调整注解参数,补充Locale并使用Jackson的日期大小写不敏感特性(需Jackson 2.13+版本支持):
@JsonProperty("createdTs") @JsonFormat( shape = JsonFormat.Shape.STRING, pattern = "dd-MMM-yy hh.mm.ss.SSSSSSSSS a z", locale = "en_US", with = {JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_VALUES} ) private ZonedDateTime createdTs;
注意:ACCEPT_CASE_INSENSITIVE_VALUES是针对字段值的大小写不敏感,和原配置中的ACCEPT_CASE_INSENSITIVE_PROPERTIES(针对属性名)不是同一个参数。
内容的提问来源于stack exchange,提问作者Catherine S.
相关产品推荐
相关产品推荐

