杰克逊:解析自定义偏移日期时间
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.JsonSerializer
和 com.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.JsonSerialize
和 com.fasterxml.jackson.databind.annotation.JsonDeserialize
:
将它们仅添加到您想要的字段
@JsonProperty("timestamp")
@JsonSerialize(using = CustomSerializer.class)
@JsonDeserialize(using = CustomDeserializer.class)
private OffsetDateTime timestamp;
有了这个,你就不需要在模块中注册自定义的序列化器,只有注解的字段会使用自定义的classes(其他OffsetDateTime
字段会使用默认的设置)。
我有一个带有时间戳的模型 属性:
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.JsonSerializer
和 com.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.JsonSerialize
和 com.fasterxml.jackson.databind.annotation.JsonDeserialize
:
@JsonProperty("timestamp")
@JsonSerialize(using = CustomSerializer.class)
@JsonDeserialize(using = CustomDeserializer.class)
private OffsetDateTime timestamp;
有了这个,你就不需要在模块中注册自定义的序列化器,只有注解的字段会使用自定义的classes(其他OffsetDateTime
字段会使用默认的设置)。