杰克逊 jsr310 中缺少 ZonedDateTimeDeserializer
Posted
技术标签:
【中文标题】杰克逊 jsr310 中缺少 ZonedDateTimeDeserializer【英文标题】:ZonedDateTimeDeserializer is missing in jackson jsr310 【发布时间】:2017-12-26 02:10:57 【问题描述】:我正在解析ZonedDateTime
,使用如下:
@JsonSerialize(using = ZonedDateTimeSerializer.class)
private ZonedDateTime expirationDateTime;
我需要能够正确反序列化这个日期。但是,杰克逊没有为此提供解串器:
com.fasterxml.jackson.datatype.jsr310.deser
它丢失的原因是什么?最常见的解决方法是什么?
更新: 这是我的场景:
我这样创建ZonedDateTime
:
ZonedDateTime.of(2017, 1, 1, 1, 1, 1, 1, ZoneOffset.UTC)
然后我像这样序列化包含日期的对象:
public static String toJSON(Object o)
ObjectMapper objectMapper = new ObjectMapper();
StringWriter sWriter = new StringWriter();
try
JsonGenerator jsonGenerator = objectMapper.getJsonFactory().createJsonGenerator(sWriter);
objectMapper.writeValue(jsonGenerator, o);
return sWriter.toString();
catch (IOException e)
throw new IllegalStateException(e);
当我尝试将其发送到 Spring MVC 控制器时:
mockMvc.perform(post("/endpoint/")
.content(toJSON(myObject))
.contentType(APPLICATION_JSON))
.andExpect(status().isOk());
进入控制器的日期对象不同。
之前:2017-01-01T01:01:01.000000001Z
之后:2017-01-01T01:01:01.000000001Z[UTC]
【问题讨论】:
Jackson 默认使用InstantDeserializer
类作为ZonedDateTime
对象,所以它应该可以工作。我在这里做了一个简单的测试,它在没有任何额外配置的情况下反序列化了值。但是,如果您已经测试过但它不起作用,您可以edit 问题并将此信息添加到其中。
是的,等一下。
控制器中的脱序列码如何?您还使用对象映射器还是在 Spring 中配置?
Spring boot 做到了,但是,我可以看到,当我通过对象映射器将对象转换为 json 或从 json 转换时,它甚至会发生。
【参考方案1】:
2017-01-01T01:01:01.000000001Z
和2017-01-01T01:01:01.000000001Z[UTC]
这两个值实际上代表了同一个瞬间,所以它们是等价的,可以毫无问题地使用(至少应该没有问题,因为它们代表的是相同的瞬间)。
唯一的细节是 Jackson 在反序列化时出于某种原因将 ZoneId
值设置为“UTC”,在这种情况下这是多余的(Z
已经告诉偏移量是“UTC”)。但它不应该影响日期值本身。
摆脱[UTC]
部分的一个非常简单的方法是将此对象转换为OffsetDateTime
(因此它保留Z
偏移量并且不使用[UTC]
区域)然后再返回到ZonedDateTime
:
ZonedDateTime z = // object with 2017-01-01T01:01:01.000000001Z[UTC] value
z = z.toOffsetDateTime().toZonedDateTime();
System.out.println(z); // 2017-01-01T01:01:01.000000001Z
之后,z
变量的值将是2017-01-01T01:01:01.000000001Z
(没有[UTC]
部分)。
但这当然不理想,因为您必须为所有日期手动执行此操作。更好的方法是编写一个自定义的反序列化器(通过扩展 com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer
),它在 UTC 时不设置时区:
public class CustomZonedDateTimeDeserializer extends InstantDeserializer<ZonedDateTime>
public CustomZonedDateTimeDeserializer()
// most parameters are the same used by InstantDeserializer
super(ZonedDateTime.class,
DateTimeFormatter.ISO_ZONED_DATE_TIME,
ZonedDateTime::from,
// when zone id is "UTC", use the ZoneOffset.UTC constant instead of the zoneId object
a -> ZonedDateTime.ofInstant(Instant.ofEpochMilli(a.value), a.zoneId.getId().equals("UTC") ? ZoneOffset.UTC : a.zoneId),
// when zone id is "UTC", use the ZoneOffset.UTC constant instead of the zoneId object
a -> ZonedDateTime.ofInstant(Instant.ofEpochSecond(a.integer, a.fraction), a.zoneId.getId().equals("UTC") ? ZoneOffset.UTC : a.zoneId),
// the same is equals to InstantDeserializer
ZonedDateTime::withZoneSameInstant, false);
然后你必须注册这个反序列化器。如果您使用ObjectMapper
,则需要将其添加到JavaTimeModule
:
ObjectMapper objectMapper = new ObjectMapper();
JavaTimeModule module = new JavaTimeModule();
// add my custom deserializer (this will affect all ZonedDateTime deserialization)
module.addDeserializer(ZonedDateTime.class, new CustomZonedDateTimeDeserializer());
objectMapper.registerModule(module);
如果你在 Spring 中配置它,配置将是这样的(未测试):
<bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean" id="pnxObjectMapper">
<property name="deserializersByType">
<map key-type="java.lang.Class">
<entry>
<key>
<value>java.time.ZonedDateTime</value>
</key>
<bean class="your.app.CustomZonedDateTimeDeserializer">
</bean>
</entry>
</map>
</property>
</bean>
【讨论】:
我希望它是库中的内置功能。谢谢!【参考方案2】:我用这个:
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(ZonedDateTime.class, new ZonedDateTimeSerializer(DateTimeFormatter.ISO_DATE_TIME));
javaTimeModule.addDeserializer(ZonedDateTime.class, InstantDeserializer.ZONED_DATE_TIME);
【讨论】:
以上是关于杰克逊 jsr310 中缺少 ZonedDateTimeDeserializer的主要内容,如果未能解决你的问题,请参考以下文章
在春季使用 JSR310 java.time 时将日期、即时序列化为 ISO 8601
如何使用 jsr310 DateTimeFormatter 解析不区分大小写的字符串?