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.