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

时间:2017-09-17 11:54:35

标签: java datetime jackson java-time datetime-parsing

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

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

时间戳的格式如下:

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

OffsetDateTime无法解析此问题:

  

com.fasterxml.jackson.databind.exc.InvalidFormatException:无法从String" 2017-09-17 13:45:42.710576 + 02":Text&#反序列化java.time.OffsetDateTime类型的值39; 2017-09-17 13:45:42.710576 + 02'无法在索引10处解析

我该如何解决这个问题?

1 个答案:

答案 0 :(得分:10)

你必须告诉杰克逊日期的格式。基本上,您有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以获取更详细的说明。

如果您想在+02中保留相同的偏移量(OffsetDateTime),请不要忘记将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位数的可选小数秒。然后我使用上面的自定义类并在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支持的最大值),并且在序列化时打印的数量完全相同。

上面的替代方法非常灵活,因为它允许在自定义类中设置格式化程序。但它也为所有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字段将使用默认设置)。