如何使用 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) 应该足够了。