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

12

我有一个模型,其中有一个时间戳属性:

class Model {
    @JsonProperty("timestamp")
    private OffsetDateTime timestamp;
}
时间戳的格式如下:
2017-09-17 13:45:42.710576+02

OffsetDateTime无法解析以下内容:

com.fasterxml.jackson.databind.exc.InvalidFormatException:无法从字符串“2017-09-17 13:45:42.710576+02”反序列化为类型java.time.OffsetDateTime:文本“2017-09-17 13:45:42.710576+02”在索引10处无法解析

我该如何修复?

1个回答

36

你必须告诉Jackson日期的格式。基本上,你有 年-月-日 接着是 时:分:秒.毫秒 和2位数字的偏移量 (+02)。所以你的模式将会是:

year-month-day hour:minute:second.microseconds+02
@JsonProperty("timestamp")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSSSSSx")
private OffsetDateTime timestamp;

查看所有日期/时间模式以获得更详细的解释。


如果要在OffsetDateTime中保留相同的偏移量(+02),请不要忘记将DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE选项调整为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文件中进行配置)。 我将以编程方式作为示例进行操作。

首先,我使用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位小数秒的可选部分。然后我使用上面的自定义类并在 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位小数(API支持的最大值为9),并且在序列化时打印完全相同的数量。


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

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

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;

有了这个,您就不需要在模块中注册自定义序列化程序,只有被注释的字段将使用自定义类(其他OffsetDateTime字段将使用默认设置)。


有趣的是,我不知道你可以像这样定义日期格式。我应该使用OffsetDateTime还是ZonedDateTime - Kwers T
我知道你可以像https://dev59.com/1V0a5IYBdhLWcg3woZ83中那样做,但我不知道它在字符串上应该是什么样子。 - Kwers T
@KwersT 它应该是这样的 yyyy-MM-dd HH:mm:ss.[SSS][SSSSSS]x - 方括号分隔符是可选的模式,因此它接受3或6个数字。我不确定6位数字模式是否应该放在前面(有时顺序很重要)。抱歉,我刚离开家,无法进行适当的测试。 - user7605325
我会开始尝试!再次感谢。 - Kwers T
@KwersT 我确认了模式必须是 yyyy-MM-dd HH:mm:ss.[SSSSSS][SSS]x。我已经更新了答案。 - user7605325
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接