Jackson Java 8 Date/time module issue jackson-modules-java8#212 mentions enabling “strict” handling via a global default:
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.
However, I am completely failing at figuring out how to enable this globally. I’ve checked configuration classes such as DeserializationFeature
and done Internet & Stack Overflow searches for this, but have not come up with an answer.
How can I enable “strict” handling of LocalDate
, such that @JsonFormat
behaves the same as @JsonFormat(lenient = OptBoolean.FALSE)
for values deserialized by Jackson?
Advertisement
Answer
You can set global leniency by com.fasterxml.jackson.databind.ObjectMapper#setDefaultLeniency
.
But this won’t fail when LocalDate
contains the time part too. Since there is strange logic in com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer
. It uses DateTimeFormatter ISO_LOCAL_TIME
as default formatter, which doesn’t allow time part, but when deserializer parses datetime, this code runs:
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); }
You can avoid this by duplicating default DateTimeFormatter
. Here is my proposal for this:
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); } }
Jackson databind 2.13
As for version 2.13 of JavaTime
module, lenient check was added for disallowing time part in LocalDate
. So workaround with a copy of DateTimeFormatter
is not needed, setDefaultLeniency(false)
should be enough.