杰克逊:解析自定义偏移日期时间

Jackson: parse custom offset date time

我有一个带有时间戳的模型 属性:

class Model {
    @JsonProperty("timestamp")
    private OffsetDateTime timestamp;
}

时间戳格式如下:

2017-09-17 13:45:42.710576+02

OffsetDateTime 无法解析:

com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type java.time.OffsetDateTime from String "2017-09-17 13:45:42.710576+02": Text '2017-09-17 13:45:42.710576+02' could not be parsed at index 10

我该如何解决这个问题?

你必须告诉 Jackson 日期是什么格式。基本上,您有 year-month-day 后跟 hour:minute:second.microseconds 和 2 位数字的偏移量 (+02)。所以你的模式将是:

@JsonProperty("timestamp")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSSSSSx")
private OffsetDateTime timestamp;

查看 all the date/time patterns 以获得更详细的解释。


如果你想在OffsetDateTime中保留相同的偏移量(+02),不要忘记将DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE option调整为false

如果此选项设置为 true(在我的测试中),结果将转换为 UTC(但它实际上转换为 Jackson 中配置的任何时区):

2017-09-17T11:45:42.710576Z

如果我设置为false,输入中使用的偏移量被保留:

2017-09-17T13:45:42.710576+02:00


上面的代码适用于小数点后 6 位数字。但如果这个数量不同,您可以使用可选模式,由 [].

分隔

示例:如果输入可以有 6 位或 3 位小数,我可以使用 pattern = "yyyy-MM-dd HH:mm:ss.[SSSSSS][SSS]x"。可选部分 [SSSSSS][SSS] 告诉解析器考虑 6 位或 3 位数字。

可选模式的问题在于,在序列化时,它会打印所有模式(因此它会打印两次秒的小数:6 3 位)。


另一种方法是创建自定义序列化器和反序列化器(通过扩展 com.fasterxml.jackson.databind.JsonSerializercom.fasterxml.jackson.databind.JsonDeserializer):

public class CustomDeserializer extends JsonDeserializer<OffsetDateTime> {

    private DateTimeFormatter formatter;

    public CustomDeserializer(DateTimeFormatter formatter) {
        this.formatter = formatter;
    }

    @Override
    public OffsetDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException, JsonProcessingException {
        return OffsetDateTime.parse(parser.getText(), this.formatter);
    }
}

public class CustomSerializer extends JsonSerializer<OffsetDateTime> {

    private DateTimeFormatter formatter;

    public CustomSerializer(DateTimeFormatter formatter) {
        this.formatter = formatter;
    }

    @Override
    public void serialize(OffsetDateTime value, JsonGenerator gen, SerializerProvider provider) throws IOException, JsonProcessingException {
        gen.writeString(value.format(this.formatter));
    }
}

然后你可以在JavaTimeModule中注册那些。如何配置这取决于您使用的环境(例如:在 Spring 中您可以在 xml files 中配置)。作为示例,我将以编程方式进行。

首先我创建格式化程序,使用 java.time.format.DateTimeFormatterBuilder:

DateTimeFormatter formatter = new DateTimeFormatterBuilder()
    // date/time
    .appendPattern("yyyy-MM-dd HH:mm:ss")
    // optional fraction of seconds (from 0 to 9 digits)
    .optionalStart().appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true).optionalEnd()
    // offset
    .appendPattern("x")
    // create formatter
    .toFormatter();

此格式化程序接受 0 到 9 位的可选小数秒。然后我使用上面的自定义 classes 并将它们注册到 ObjectMapper:

// set formatter in the module and register in object mapper
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE, false);
JavaTimeModule module = new JavaTimeModule();
module.addSerializer(OffsetDateTime.class, new CustomSerializer(formatter));
module.addDeserializer(OffsetDateTime.class, new CustomDeserializer(formatter));
mapper.registerModule(module);

我还从字段中删除了 @JsonFormat 注释:

@JsonProperty("timestamp")
private OffsetDateTime timestamp;

现在它接受像 2017-09-17 13:45:42+02(没有小数秒)和 2017-09-17 13:45:42.71014+02(5 位小数)这样的值。它可以解析 0 到 9 个十进制数字(9 是 API 支持的最大值),并且在序列化时打印完全相同的数量。


上面的替代方案非常灵活,因为它允许在自定义 classes 中设置格式化程序。但它也为所有 OffsetDateTime 字段设置序列化和反序列化。

如果你不想这样,你也可以创建一个 class 具有固定格式化程序:

static class CustomDeserializer extends JsonDeserializer<OffsetDateTime> {

    private DateTimeFormatter formatter = // create formatter as above

    // deserialize method is the same
}

static class CustomSerializer extends JsonSerializer<OffsetDateTime> {

    private DateTimeFormatter formatter = // create formatter as above

    // serialize method is the same
}

然后,您可以使用注释 com.fasterxml.jackson.databind.annotation.JsonSerializecom.fasterxml.jackson.databind.annotation.JsonDeserialize:

将它们仅添加到您想要的字段
@JsonProperty("timestamp")
@JsonSerialize(using = CustomSerializer.class)
@JsonDeserialize(using = CustomDeserializer.class)
private OffsetDateTime timestamp;

有了这个,你就不需要在模块中注册自定义的序列化器,只有注解的字段会使用自定义的classes(其他OffsetDateTime字段会使用默认的设置)。