如何使用 Jackson 全局启用 "strict" 处理 LocalDate 值?
How do I globally enable "strict" handling of LocalDate values using Jackson?
Jackson Java 8 Date/time module issue jackson-modules-java8#212 提到通过全局默认值启用“严格”处理:
Currently LocalDateDeserializer
automatically accepts non-standard format where time part also exists.
While this is useful for some use cases, compatibility, it seems reasonable that if user forces "strict" handling (via @JsonFormat
, or global default), that part would not be accepted.
但是,我完全没有弄清楚如何在全球范围内启用它。我检查了配置 类,例如 DeserializationFeature
,并完成了 Internet & Stack Overflow 搜索,但没有找到答案。
如何启用对 LocalDate
的“严格”处理,以便 @JsonFormat
对于 Jackson 反序列化的值的行为与 @JsonFormat(lenient = OptBoolean.FALSE)
相同?
您可以通过com.fasterxml.jackson.databind.ObjectMapper#setDefaultLeniency
设置全局宽大。
但是当 LocalDate
也包含时间部分时,这不会失败。由于com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer
中有奇怪的逻辑。它使用 DateTimeFormatter ISO_LOCAL_TIME
作为默认格式化程序,不允许时间部分,但是当反序列化程序解析日期时间时,此代码运行:
try {
// as per [datatype-jsr310#37], only check for optional (and, incorrect...) time marker 'T'
// if we are using default formatter
DateTimeFormatter format = _formatter;
if (format == DEFAULT_FORMATTER) {
// JavaScript by default includes time in JSON serialized Dates (UTC/ISO instant format).
if (string.length() > 10 && string.charAt(10) == 'T') {
if (string.endsWith("Z")) {
return LocalDateTime.ofInstant(Instant.parse(string), ZoneOffset.UTC).toLocalDate();
} else {
return LocalDate.parse(string, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
}
}
}
return LocalDate.parse(string, format);
} catch (DateTimeException e) {
return _handleDateTimeException(ctxt, e, string);
}
您可以通过复制默认 DateTimeFormatter
来避免这种情况。这是我对此的建议:
class ObjectMapperPlayground {
@Data
static class Test {
LocalDate date;
}
private static DateTimeFormatter clone(DateTimeFormatter dtf) {
ResolverStyle oldStyle = dtf.getResolverStyle();
ResolverStyle newStyle = Arrays.stream(ResolverStyle.values())
.filter(Predicate.not(Predicate.isEqual(oldStyle)))
.findAny()
// should never happen
.orElseThrow(() -> new IllegalStateException("ResolverStyle enum has single value."));
// DateTimeFormatter has a check which makes our life a little harder :)
/*
public DateTimeFormatter withResolverStyle(ResolverStyle resolverStyle) {
Objects.requireNonNull(resolverStyle, "resolverStyle");
if (Objects.equals(this.resolverStyle, resolverStyle)) {
return this;
}
return new DateTimeFormatter(printerParser, locale, decimalStyle, resolverStyle, resolverFields, chrono, zone);
}
*/
return dtf.withResolverStyle(newStyle).withResolverStyle(oldStyle);
}
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper regularMapper = new ObjectMapper()
// no clone here
.registerModule(new JavaTimeModule().addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ISO_LOCAL_DATE)))
.setDefaultLeniency(false);
// OK
regularMapper.readValue("{\"date\":\"2021-01-01T12:12:12\"}", Test.class);
ObjectMapper dupMapper = new ObjectMapper()
// with clone
.registerModule(new JavaTimeModule().addDeserializer(LocalDate.class, new LocalDateDeserializer(clone(DateTimeFormatter.ISO_LOCAL_DATE))))
.setDefaultLeniency(false);
// throws
dupMapper.readValue("{\"date\":\"2021-01-01T12:12:12\"}", Test.class);
}
}
杰克逊数据绑定 2.13
对于 JavaTime
模块的 2.13 版,在 LocalDate
中添加了不允许时间部分的宽松检查。因此不需要 DateTimeFormatter
副本的解决方法,setDefaultLeniency(false)
应该足够了。
Jackson Java 8 Date/time module issue jackson-modules-java8#212 提到通过全局默认值启用“严格”处理:
Currently
LocalDateDeserializer
automatically accepts non-standard format where time part also exists. While this is useful for some use cases, compatibility, it seems reasonable that if user forces "strict" handling (via@JsonFormat
, or global default), that part would not be accepted.
但是,我完全没有弄清楚如何在全球范围内启用它。我检查了配置 类,例如 DeserializationFeature
,并完成了 Internet & Stack Overflow 搜索,但没有找到答案。
如何启用对 LocalDate
的“严格”处理,以便 @JsonFormat
对于 Jackson 反序列化的值的行为与 @JsonFormat(lenient = OptBoolean.FALSE)
相同?
您可以通过com.fasterxml.jackson.databind.ObjectMapper#setDefaultLeniency
设置全局宽大。
但是当 LocalDate
也包含时间部分时,这不会失败。由于com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer
中有奇怪的逻辑。它使用 DateTimeFormatter ISO_LOCAL_TIME
作为默认格式化程序,不允许时间部分,但是当反序列化程序解析日期时间时,此代码运行:
try {
// as per [datatype-jsr310#37], only check for optional (and, incorrect...) time marker 'T'
// if we are using default formatter
DateTimeFormatter format = _formatter;
if (format == DEFAULT_FORMATTER) {
// JavaScript by default includes time in JSON serialized Dates (UTC/ISO instant format).
if (string.length() > 10 && string.charAt(10) == 'T') {
if (string.endsWith("Z")) {
return LocalDateTime.ofInstant(Instant.parse(string), ZoneOffset.UTC).toLocalDate();
} else {
return LocalDate.parse(string, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
}
}
}
return LocalDate.parse(string, format);
} catch (DateTimeException e) {
return _handleDateTimeException(ctxt, e, string);
}
您可以通过复制默认 DateTimeFormatter
来避免这种情况。这是我对此的建议:
class ObjectMapperPlayground {
@Data
static class Test {
LocalDate date;
}
private static DateTimeFormatter clone(DateTimeFormatter dtf) {
ResolverStyle oldStyle = dtf.getResolverStyle();
ResolverStyle newStyle = Arrays.stream(ResolverStyle.values())
.filter(Predicate.not(Predicate.isEqual(oldStyle)))
.findAny()
// should never happen
.orElseThrow(() -> new IllegalStateException("ResolverStyle enum has single value."));
// DateTimeFormatter has a check which makes our life a little harder :)
/*
public DateTimeFormatter withResolverStyle(ResolverStyle resolverStyle) {
Objects.requireNonNull(resolverStyle, "resolverStyle");
if (Objects.equals(this.resolverStyle, resolverStyle)) {
return this;
}
return new DateTimeFormatter(printerParser, locale, decimalStyle, resolverStyle, resolverFields, chrono, zone);
}
*/
return dtf.withResolverStyle(newStyle).withResolverStyle(oldStyle);
}
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper regularMapper = new ObjectMapper()
// no clone here
.registerModule(new JavaTimeModule().addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ISO_LOCAL_DATE)))
.setDefaultLeniency(false);
// OK
regularMapper.readValue("{\"date\":\"2021-01-01T12:12:12\"}", Test.class);
ObjectMapper dupMapper = new ObjectMapper()
// with clone
.registerModule(new JavaTimeModule().addDeserializer(LocalDate.class, new LocalDateDeserializer(clone(DateTimeFormatter.ISO_LOCAL_DATE))))
.setDefaultLeniency(false);
// throws
dupMapper.readValue("{\"date\":\"2021-01-01T12:12:12\"}", Test.class);
}
}
杰克逊数据绑定 2.13
对于 JavaTime
模块的 2.13 版,在 LocalDate
中添加了不允许时间部分的宽松检查。因此不需要 DateTimeFormatter
副本的解决方法,setDefaultLeniency(false)
应该足够了。