杰克逊JSR310中缺少ZonedDateTimeDeserializer

dve*_*opp 5 java jaxb date-parsing jackson zoneddatetime

我正在解析ZonedDateTime这样的用法:

 @JsonSerialize(using = ZonedDateTimeSerializer.class)
 private ZonedDateTime expirationDateTime;
Run Code Online (Sandbox Code Playgroud)

我需要能够正确反序列化这个日期。但是,杰克逊没有为此提供反序列化器:

com.fasterxml.jackson.datatype.jsr310.deser
Run Code Online (Sandbox Code Playgroud)

有什么原因会丢失它吗?最常见的解决方法是什么?

更新:这是我的情况:

我这样创建ZonedDateTime

ZonedDateTime.of(2017, 1, 1, 1, 1, 1, 1, ZoneOffset.UTC)
Run Code Online (Sandbox Code Playgroud)

然后我序列化包含日期的对象,如下所示:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

当我尝试将其发送到Spring MVC Controller时:

    mockMvc.perform(post("/endpoint/")
            .content(toJSON(myObject))
            .contentType(APPLICATION_JSON))
            .andExpect(status().isOk());
Run Code Online (Sandbox Code Playgroud)

控制器内部的日期对象不同。

之前: 2017-01-01T01:01:01.000000001Z

后: 2017-01-01T01:01:01.000000001Z[UTC]

小智 5

这两个值2017-01-01T01:01:01.000000001Z2017-01-01T01:01:01.000000001Z[UTC]实际上代表同一时刻,因此它们是等效的并且可以毫无问题地使用(至少应该没有问题,因为它们代表同一时刻)。

唯一的细节是杰克逊出于某种原因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
Run Code Online (Sandbox Code Playgroud)

之后,z变量的值将是2017-01-01T01:01:01.000000001Z(没有[UTC]部分)。

但当然这并不理想,因为您必须为所有日期手动执行此操作。更好的方法是编写一个自定义反序列化器(通过扩展),当它是UTCcom.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer时不设置时区:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你必须注册这个解串器。如果您使用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);
Run Code Online (Sandbox Code Playgroud)

如果你在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>
Run Code Online (Sandbox Code Playgroud)