From 8b30c9bf6db66182d90c1c3fbc6cd89a0b39834c Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Fri, 26 Mar 2021 09:09:46 -0700 Subject: [PATCH] Simplify handling of `LocalDate` value with invalid trailing time-portion (#211) Fixed #212: Simplify handling of `LocalDate` value with invalid trailing time-portion; also take strict/lenient into account. --- .../jsr310/deser/LocalDateDeserializer.java | 20 +++++++---- .../jsr310/deser/LocalDateDeserTest.java | 36 +++++++++++++++++-- .../jsr310/failing/LocalDateDeserTest.java | 29 --------------- release-notes/VERSION-2.x | 3 +- 4 files changed, 48 insertions(+), 40 deletions(-) delete mode 100644 datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/failing/LocalDateDeserTest.java diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/LocalDateDeserializer.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/LocalDateDeserializer.java index dd175136..825290fa 100644 --- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/LocalDateDeserializer.java +++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/LocalDateDeserializer.java @@ -18,16 +18,15 @@ import java.io.IOException; import java.time.DateTimeException; -import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; -import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.core.*; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JavaType; /** * Deserializer for Java 8 temporal {@link LocalDate}s. @@ -154,11 +153,18 @@ protected LocalDate _fromString(JsonParser p, DeserializationContext ctxt, 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); - } + if (isLenient()) { + if (string.endsWith("Z")) { + return LocalDate.parse(string.substring(0, string.length() - 1), + DateTimeFormatter.ISO_LOCAL_DATE_TIME); + } + return LocalDate.parse(string, DateTimeFormatter.ISO_LOCAL_DATE_TIME); + } + JavaType t = getValueType(ctxt); + return (LocalDate) ctxt.handleWeirdStringValue(t.getRawClass(), + string, +"Should not contain time component when 'strict' mode set for property or type (enable 'lenient' handling to allow)" + ); } } return LocalDate.parse(string, format); diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/LocalDateDeserTest.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/LocalDateDeserTest.java index a46584ea..d6a603fa 100644 --- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/LocalDateDeserTest.java +++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/LocalDateDeserTest.java @@ -83,6 +83,15 @@ public StrictWrapperWithYearWithoutEra() { } public StrictWrapperWithYearWithoutEra(LocalDate v) { value = v; } } + static class StrictWrapperWithFormat { + @JsonFormat(pattern="yyyy-MM-dd", + lenient = OptBoolean.FALSE) + public LocalDate value; + + public StrictWrapperWithFormat() { } + public StrictWrapperWithFormat(LocalDate v) { value = v; } + } + /* /********************************************************** /* Deserialization from Int array representation @@ -324,6 +333,21 @@ public void testCustomFormat() throws Exception assertEquals(28, date.getDayOfMonth()); } + @Test + public void testStrictCustomFormat() throws Exception + { + try { + /*StrictWrapperWithFormat w = */ MAPPER.readValue( + "{\"value\":\"2019-11-30\"}", + StrictWrapperWithFormat.class); + fail("Should not pass"); + } catch (InvalidFormatException e) { + // 25-Mar-2021, tatu: Really bad exception message we got... but + // it is what it is + verifyException(e, "Cannot deserialize value of type `java.time.LocalDate` from String"); + verifyException(e, "\"2019-11-30\""); + } + } /* /********************************************************** @@ -402,7 +426,9 @@ public void testDeserializationCaseInsensitiveEnabledOnValue() throws Throwable @Test public void testDeserializationCaseInsensitiveEnabled() throws Throwable { - ObjectMapper mapper = newMapper().configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_VALUES, true); + ObjectMapper mapper = mapperBuilder() + .enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_VALUES) + .build(); mapper.configOverride(LocalDate.class).setFormat(JsonFormat.Value.forPattern("dd-MMM-yyyy")); ObjectReader reader = mapper.readerFor(LocalDate.class); String[] jsons = new String[] {"'01-Jan-2000'","'01-JAN-2000'", "'01-jan-2000'"}; @@ -414,7 +440,9 @@ public void testDeserializationCaseInsensitiveEnabled() throws Throwable @Test public void testDeserializationCaseInsensitiveDisabled() throws Throwable { - ObjectMapper mapper = newMapper().configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_VALUES, false); + ObjectMapper mapper = mapperBuilder() + .disable(MapperFeature.ACCEPT_CASE_INSENSITIVE_VALUES) + .build(); mapper.configOverride(LocalDate.class).setFormat(JsonFormat.Value.forPattern("dd-MMM-yyyy")); ObjectReader reader = mapper.readerFor(LocalDate.class); expectSuccess(reader, LocalDate.of(2000, Month.JANUARY, 1), "'01-Jan-2000'"); @@ -423,7 +451,9 @@ public void testDeserializationCaseInsensitiveDisabled() throws Throwable @Test public void testDeserializationCaseInsensitiveDisabled_InvalidDate() throws Throwable { - ObjectMapper mapper = newMapper().configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_VALUES, false); + ObjectMapper mapper = mapperBuilder() + .disable(MapperFeature.ACCEPT_CASE_INSENSITIVE_VALUES) + .build(); mapper.configOverride(LocalDate.class).setFormat(JsonFormat.Value.forPattern("dd-MMM-yyyy")); ObjectReader reader = mapper.readerFor(LocalDate.class); String[] jsons = new String[] {"'01-JAN-2000'", "'01-jan-2000'"}; diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/failing/LocalDateDeserTest.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/failing/LocalDateDeserTest.java deleted file mode 100644 index 0bec8992..00000000 --- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/failing/LocalDateDeserTest.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.fasterxml.jackson.datatype.jsr310.failing; - -import com.fasterxml.jackson.annotation.JsonFormat; -import com.fasterxml.jackson.annotation.OptBoolean; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.exc.InvalidFormatException; -import com.fasterxml.jackson.datatype.jsr310.ModuleTestBase; -import org.junit.Test; - -import java.time.LocalDate; - -public class LocalDateDeserTest extends ModuleTestBase { - private final ObjectMapper MAPPER = newMapper(); - - final static class StrictWrapper { - @JsonFormat(pattern="yyyy-MM-dd", - lenient = OptBoolean.FALSE) - public LocalDate value; - - public StrictWrapper() { } - public StrictWrapper(LocalDate v) { value = v; } - } - - @Test(expected = InvalidFormatException.class) - public void testStrictCustomFormat() throws Exception - { - /*StrictWrapper w =*/ MAPPER.readValue("{\"value\":\"2019-11-30\"}", StrictWrapper.class); - } -} diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index 1ae51502..418b43e1 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -10,7 +10,8 @@ Modules: 2.13.0 (not yet released) -No changes since 2.12 +#212: Make LocalDateDeserializer consider strict/lenient on accepting (or not) + of "time" part 2.12.3 (not yet released)