From d92e1a7ce51e71027b4d3a389c9c1ab2af196edf Mon Sep 17 00:00:00 2001 From: michaelok Date: Tue, 15 Oct 2019 22:41:39 -0500 Subject: [PATCH 1/9] per code review, add test specifically from issue where default mapper is used --- .../jsr310/deser/DurationDeserializer.java | 42 +++++++++++++- .../jsr310/deser/InstantDeserializer.java | 4 +- .../jsr310/deser/JSR310DeserializerBase.java | 58 ++++++++++++++++++- .../JSR310StringParsableDeserializer.java | 6 ++ .../jsr310/deser/DurationDeserTest.java | 48 +++++++++++++++ 5 files changed, 151 insertions(+), 7 deletions(-) diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/DurationDeserializer.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/DurationDeserializer.java index cba6e3b7..5cbebd7e 100644 --- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/DurationDeserializer.java +++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/DurationDeserializer.java @@ -16,17 +16,21 @@ package com.fasterxml.jackson.datatype.jsr310.deser; +import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.core.JsonTokenId; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.deser.ContextualDeserializer; import com.fasterxml.jackson.datatype.jsr310.DecimalUtils; import java.io.IOException; import java.math.BigDecimal; import java.time.DateTimeException; import java.time.Duration; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.util.Locale; /** * Deserializer for Java 8 temporal {@link Duration}s. @@ -34,7 +38,7 @@ * @author Nick Williams * @since 2.2.0 */ -public class DurationDeserializer extends JSR310DeserializerBase +public class DurationDeserializer extends JSR310DeserializerBase implements ContextualDeserializer { private static final long serialVersionUID = 1L; @@ -45,6 +49,18 @@ private DurationDeserializer() super(Duration.class); } + /** + * Since 2.11 + */ + public DurationDeserializer(DurationDeserializer base, Boolean leniency) { + super(base, leniency); + } + + @Override + protected DurationDeserializer withLeniency(Boolean leniency) { + return new DurationDeserializer(this, leniency); + } + @Override public Duration deserialize(JsonParser parser, DeserializationContext context) throws IOException { @@ -63,6 +79,9 @@ public Duration deserialize(JsonParser parser, DeserializationContext context) t case JsonTokenId.ID_STRING: String string = parser.getText().trim(); if (string.length() == 0) { + if (!isLenient()) { + return _failForNotLenient(parser, context, JsonToken.VALUE_STRING); + } return null; } try { @@ -81,4 +100,21 @@ public Duration deserialize(JsonParser parser, DeserializationContext context) t return _handleUnexpectedToken(context, parser, JsonToken.VALUE_STRING, JsonToken.VALUE_NUMBER_INT, JsonToken.VALUE_NUMBER_FLOAT); } + + @Override + public JsonDeserializer createContextual(DeserializationContext ctxt, + BeanProperty property) throws JsonMappingException + { + JsonFormat.Value format = findFormatOverrides(ctxt, property, handledType()); + DurationDeserializer deser = this; + if (format != null) { + if (format.hasLenient()) { + Boolean leniency = format.getLenient(); + if (leniency != null) { + deser = deser.withLeniency(leniency); + } + } + } + return deser; + } } diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializer.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializer.java index c2a86bab..772763b5 100644 --- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializer.java +++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializer.java @@ -186,7 +186,9 @@ public T deserialize(JsonParser parser, DeserializationContext context) throws I { String string = parser.getText().trim(); if (string.length() == 0) { - return null; + if (!isLenient()) { + return _failForNotLenient(parser, context, JsonToken.VALUE_STRING); + } } // only check for other parsing modes if we are using default formatter if (_formatter == DateTimeFormatter.ISO_INSTANT || diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310DeserializerBase.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310DeserializerBase.java index 2be13791..a075d011 100644 --- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310DeserializerBase.java +++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310DeserializerBase.java @@ -27,6 +27,7 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer; import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; +import com.fasterxml.jackson.databind.util.ClassUtil; /** * Base class that indicates that all JSR310 datatypes are deserialized from scalar JSON types. @@ -38,15 +39,57 @@ abstract class JSR310DeserializerBase extends StdScalarDeserializer { private static final long serialVersionUID = 1L; + /** + * Flag that indicates what leniency setting is enabled for this deserializer (either + * due {@link JsonFormat} annotation on property or class, or due to per-type + * "config override", or from global settings): leniency/strictness has effect + * on accepting some non-default input value representations (such as integer values + * for dates). + *

+ * Note that global default setting is for leniency to be enabled, for Jackson 2.x, + * and has to be explicitly change to force strict handling: this is to keep backwards + * compatibility with earlier versions. + * + * @since 2.11 + */ + protected final boolean _isLenient; + + /** + * @since 2.11 + */ protected JSR310DeserializerBase(Class supportedType) { super(supportedType); + _isLenient = true; + } + + protected JSR310DeserializerBase(Class supportedType, + Boolean leniency) { + super(supportedType); + _isLenient = !Boolean.FALSE.equals(leniency); + } + + protected JSR310DeserializerBase(JSR310DeserializerBase base) { + super(base); + _isLenient = true; + } + + protected JSR310DeserializerBase(JSR310DeserializerBase base, Boolean leniency) { + super(base); + _isLenient = !Boolean.FALSE.equals(leniency); } /** - * @since 2.10 + * @since 2.11 */ - protected JSR310DeserializerBase(JSR310DeserializerBase base) { - super(base); + protected abstract JSR310DeserializerBase withLeniency(Boolean leniency); + + /** + * @return {@code true} if lenient handling is enabled; {code false} if not (strict mode) + * + * @since 2.11 + */ + protected boolean isLenient() { + return _isLenient; } @Override @@ -122,6 +165,15 @@ protected R _handleUnexpectedToken(DeserializationContext context, handledType().getName()); } + @SuppressWarnings("unchecked") + protected T _failForNotLenient(JsonParser p, DeserializationContext ctxt, + JsonToken expToken) throws IOException + { + return (T) ctxt.handleUnexpectedToken(handledType(), expToken, p, + "Cannot deserialize instance of %s out of %s token: not allowed because 'strict' mode set for property or type (enable 'lenient' handling to allow)", + ClassUtil.nameOf(handledType()), p.currentToken()); + } + /** * Helper method used to peel off spurious wrappings of DateTimeException * diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310StringParsableDeserializer.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310StringParsableDeserializer.java index 24b2b862..000b77ca 100644 --- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310StringParsableDeserializer.java +++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310StringParsableDeserializer.java @@ -71,6 +71,12 @@ protected static JsonDeserializer createDeserializer(Class type, int t return (JsonDeserializer) new JSR310StringParsableDeserializer(type, typeId); } + @Override + protected JSR310StringParsableDeserializer withLeniency(Boolean leniency) { + // TODO: implement! + return null; + } + @Override public Object deserialize(JsonParser parser, DeserializationContext context) throws IOException { diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/DurationDeserTest.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/DurationDeserTest.java index 68d2191a..501bef24 100644 --- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/DurationDeserTest.java +++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/DurationDeserTest.java @@ -2,8 +2,12 @@ import java.math.BigInteger; import java.time.Duration; +import java.time.LocalDateTime; import java.time.temporal.TemporalAmount; +import java.util.Map; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.core.type.TypeReference; import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -24,6 +28,8 @@ public class DurationDeserTest extends ModuleTestBase { private final ObjectReader READER = newMapper().readerFor(Duration.class); + private final TypeReference> MAP_TYPE_REF = new TypeReference>() { }; + @Test public void testDeserializationAsFloat01() throws Exception { @@ -371,4 +377,46 @@ public void testDeserializationAsEmptyArrayEnabled() throws Throwable .readerFor(Duration.class).readValue(aposToQuotes(json)); assertNull(value); } + + @Test + public void testLenientDeserializeFromEmptyString() throws Exception { + + String key = "duration"; + ObjectMapper mapper = newMapper(); + ObjectReader objectReader = mapper.readerFor(MAP_TYPE_REF); + + String dateValAsNullStr = null; + String dateValAsEmptyStr = ""; + + String valueFromNullStr = mapper.writeValueAsString(asMap(key, dateValAsNullStr)); + Map actualMapFromNullStr = objectReader.readValue(valueFromNullStr); + Duration actualDateFromNullStr = actualMapFromNullStr.get(key); + assertNull(actualDateFromNullStr); + + String valueFromEmptyStr = mapper.writeValueAsString(asMap(key, dateValAsEmptyStr)); + Map actualMapFromEmptyStr = objectReader.readValue(valueFromEmptyStr); + Duration actualDateFromEmptyStr = actualMapFromEmptyStr.get(key); + assertEquals("empty string failed to deserialize to null with lenient setting", null, actualDateFromEmptyStr); + } + + @Test ( expected = MismatchedInputException.class) + public void testStrictDeserializeFromEmptyString() throws Exception { + + final String key = "duration"; + final ObjectMapper mapper = mapperBuilder().build(); + mapper.configOverride(Duration.class) + .setFormat(JsonFormat.Value.forLeniency(false)); + + final ObjectReader objectReader = mapper.readerFor(MAP_TYPE_REF); + final String dateValAsNullStr = null; + + // even with strict, null value should be deserialized without throwing an exception + String valueFromNullStr = mapper.writeValueAsString(asMap(key, dateValAsNullStr)); + Map actualMapFromNullStr = objectReader.readValue(valueFromNullStr); + assertNull(actualMapFromNullStr.get(key)); + + String dateValAsEmptyStr = ""; + String valueFromEmptyStr = mapper.writeValueAsString(asMap(key, dateValAsEmptyStr)); + objectReader.readValue(valueFromEmptyStr); + } } From c42f1215d40554796bffc1b6554d75445f73fa35 Mon Sep 17 00:00:00 2001 From: michaelok Date: Wed, 16 Oct 2019 00:25:47 -0500 Subject: [PATCH 2/9] per code review, add test specifically from issue where default mapper is used --- .../jsr310/deser/LocalTimeDeserializer.java | 13 ++++- .../jsr310/deser/OffsetTimeDeserializer.java | 13 ++++- .../jsr310/deser/YearMonthDeserializer.java | 13 ++++- .../jsr310/deser/LocalTimeDeserTest.java | 48 ++++++++++++++++++ .../jsr310/deser/MonthDayDeserTest.java | 27 ++++++++++ .../jsr310/deser/YearMonthDeserTest.java | 50 +++++++++++++++++++ 6 files changed, 158 insertions(+), 6 deletions(-) diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/LocalTimeDeserializer.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/LocalTimeDeserializer.java index 14528758..2a09eede 100644 --- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/LocalTimeDeserializer.java +++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/LocalTimeDeserializer.java @@ -48,15 +48,21 @@ public LocalTimeDeserializer(DateTimeFormatter formatter) { super(LocalTime.class, formatter); } + /** + * Since 2.11 + */ + protected LocalTimeDeserializer(LocalTimeDeserializer base, Boolean leniency) { + super(base, leniency); + } + @Override protected LocalTimeDeserializer withDateFormat(DateTimeFormatter formatter) { return new LocalTimeDeserializer(formatter); } - // !!! TODO: lenient vs strict? @Override protected LocalTimeDeserializer withLeniency(Boolean leniency) { - return this; + return new LocalTimeDeserializer(this, leniency); } @Override @@ -68,6 +74,9 @@ public LocalTime deserialize(JsonParser parser, DeserializationContext context) if (parser.hasToken(JsonToken.VALUE_STRING)) { String string = parser.getText().trim(); if (string.length() == 0) { + if (!isLenient()) { + return _failForNotLenient(parser, context, JsonToken.VALUE_STRING); + } return null; } DateTimeFormatter format = _formatter; diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/OffsetTimeDeserializer.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/OffsetTimeDeserializer.java index d8281196..8c703428 100644 --- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/OffsetTimeDeserializer.java +++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/OffsetTimeDeserializer.java @@ -45,15 +45,21 @@ protected OffsetTimeDeserializer(DateTimeFormatter dtf) { super(OffsetTime.class, dtf); } + /** + * Since 2.11 + */ + protected OffsetTimeDeserializer(OffsetTimeDeserializer base, Boolean leniency) { + super(base, leniency); + } + @Override protected OffsetTimeDeserializer withDateFormat(DateTimeFormatter dtf) { return new OffsetTimeDeserializer(dtf); } - // !!! TODO: lenient vs strict? @Override protected OffsetTimeDeserializer withLeniency(Boolean leniency) { - return this; + return new OffsetTimeDeserializer(this, leniency); } @Override @@ -65,6 +71,9 @@ public OffsetTime deserialize(JsonParser parser, DeserializationContext context) if (parser.hasToken(JsonToken.VALUE_STRING)) { String string = parser.getText().trim(); if (string.length() == 0) { + if (!isLenient()) { + return _failForNotLenient(parser, context, JsonToken.VALUE_STRING); + } return null; } try { diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/YearMonthDeserializer.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/YearMonthDeserializer.java index 9e5a05c3..27ecf4e7 100644 --- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/YearMonthDeserializer.java +++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/YearMonthDeserializer.java @@ -49,15 +49,21 @@ public YearMonthDeserializer(DateTimeFormatter formatter) super(YearMonth.class, formatter); } + /** + * Since 2.11 + */ + protected YearMonthDeserializer(YearMonthDeserializer base, Boolean leniency) { + super(base, leniency); + } + @Override protected YearMonthDeserializer withDateFormat(DateTimeFormatter dtf) { return new YearMonthDeserializer(dtf); } - // !!! TODO: lenient vs strict? @Override protected YearMonthDeserializer withLeniency(Boolean leniency) { - return this; + return new YearMonthDeserializer(this, leniency); } @Override @@ -69,6 +75,9 @@ public YearMonth deserialize(JsonParser parser, DeserializationContext context) if (parser.hasToken(JsonToken.VALUE_STRING)) { String string = parser.getText().trim(); if (string.length() == 0) { + if (!isLenient()) { + return _failForNotLenient(parser, context, JsonToken.VALUE_STRING); + } return null; } try { diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/LocalTimeDeserTest.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/LocalTimeDeserTest.java index 072e8fde..69e30d4e 100644 --- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/LocalTimeDeserTest.java +++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/LocalTimeDeserTest.java @@ -17,9 +17,12 @@ package com.fasterxml.jackson.datatype.jsr310.deser; import java.io.IOException; +import java.time.LocalDate; +import java.time.LocalDateTime; import java.time.LocalTime; import java.time.format.DateTimeParseException; import java.time.temporal.Temporal; +import java.util.Map; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -27,7 +30,9 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectReader; @@ -40,6 +45,7 @@ public class LocalTimeDeserTest extends ModuleTestBase { private ObjectReader reader = newMapper().readerFor(LocalTime.class); + private final TypeReference> MAP_TYPE_REF = new TypeReference>() { }; @Test public void testDeserializationAsTimestamp01() throws Exception @@ -209,6 +215,48 @@ public void testDeserializationWithTypeInfo03() throws Exception assertEquals("The value is not correct.", time, value); } + /* + /********************************************************** + /* Tests for empty string handling + /********************************************************** + */ + + @Test + public void testLenientDeserializeFromEmptyString() throws Exception { + + String key = "localTime"; + ObjectMapper mapper = newMapper(); + ObjectReader objectReader = mapper.readerFor(MAP_TYPE_REF); + + String dateValAsEmptyStr = ""; + + String valueFromNullStr = mapper.writeValueAsString(asMap(key, null)); + Map actualMapFromNullStr = objectReader.readValue(valueFromNullStr); + LocalTime actualDateFromNullStr = actualMapFromNullStr.get(key); + assertNull(actualDateFromNullStr); + + String valueFromEmptyStr = mapper.writeValueAsString(asMap(key, dateValAsEmptyStr)); + Map actualMapFromEmptyStr = objectReader.readValue(valueFromEmptyStr); + LocalTime actualDateFromEmptyStr = actualMapFromEmptyStr.get(key); + assertEquals("empty string failed to deserialize to null with lenient setting",null, actualDateFromEmptyStr); + } + + @Test( expected = MismatchedInputException.class) + public void testStrictDeserializFromEmptyString() throws Exception { + + final String key = "localTime"; + final ObjectMapper mapper = mapperBuilder().build(); + mapper.configOverride(LocalTime.class) + .setFormat(JsonFormat.Value.forLeniency(false)); + final ObjectReader objectReader = mapper.readerFor(MAP_TYPE_REF); + + String valueFromNullStr = mapper.writeValueAsString(asMap(key, null)); + Map actualMapFromNullStr = objectReader.readValue(valueFromNullStr); + assertNull(actualMapFromNullStr.get(key)); + + String valueFromEmptyStr = mapper.writeValueAsString(asMap("date", "")); + objectReader.readValue(valueFromEmptyStr); + } private void expectFailure(String aposJson) throws Throwable { try { diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/MonthDayDeserTest.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/MonthDayDeserTest.java index d0c4a63d..50add4f8 100644 --- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/MonthDayDeserTest.java +++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/MonthDayDeserTest.java @@ -2,10 +2,12 @@ import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectReader; +import com.fasterxml.jackson.databind.exc.MismatchedInputException; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.datatype.jsr310.MockObjectConfiguration; import com.fasterxml.jackson.datatype.jsr310.ModuleTestBase; @@ -13,10 +15,12 @@ import org.junit.Test; import java.io.IOException; +import java.time.LocalTime; import java.time.Month; import java.time.MonthDay; import java.time.format.DateTimeParseException; import java.time.temporal.TemporalAccessor; +import java.util.Map; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -27,6 +31,7 @@ public class MonthDayDeserTest extends ModuleTestBase { private final ObjectMapper MAPPER = newMapper(); private final ObjectReader READER = MAPPER.readerFor(MonthDay.class); + private final TypeReference> MAP_TYPE_REF = new TypeReference>() { }; static class Wrapper { @JsonFormat(pattern="MM/dd") @@ -151,6 +156,28 @@ public void testFormatAnnotationArray() throws Exception assertEquals(input.value, output.value); } + /* + /********************************************************** + /* Tests for empty string handling + /********************************************************** + */ + + @Test( expected = MismatchedInputException.class) + public void testStrictDeserializFromEmptyString() throws Exception { + + final String key = "monthDay"; + final ObjectMapper mapper = mapperBuilder().build(); + // leniency has no effect here because MonthDayDeser passes through to Java API MonthDay.parse method... + final ObjectReader objectReader = mapper.readerFor(MAP_TYPE_REF); + + String valueFromNullStr = mapper.writeValueAsString(asMap(key, null)); + Map actualMapFromNullStr = objectReader.readValue(valueFromNullStr); + assertNull(actualMapFromNullStr.get(key)); + + String valueFromEmptyStr = mapper.writeValueAsString(asMap(key, "")); + objectReader.readValue(valueFromEmptyStr); + } + private void expectFailure(String aposJson) throws Throwable { try { read(aposJson); diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/YearMonthDeserTest.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/YearMonthDeserTest.java index 9034a0d6..a4f32e12 100644 --- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/YearMonthDeserTest.java +++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/YearMonthDeserTest.java @@ -1,17 +1,24 @@ package com.fasterxml.jackson.datatype.jsr310.deser; +import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectReader; +import com.fasterxml.jackson.databind.exc.MismatchedInputException; import com.fasterxml.jackson.datatype.jsr310.ModuleTestBase; import org.junit.Test; import java.io.IOException; +import java.time.LocalTime; import java.time.Month; +import java.time.MonthDay; import java.time.YearMonth; import java.time.format.DateTimeParseException; +import java.util.Map; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -21,6 +28,7 @@ public class YearMonthDeserTest extends ModuleTestBase { private final ObjectReader READER = newMapper().readerFor(YearMonth.class); + private final TypeReference> MAP_TYPE_REF = new TypeReference>() { }; @Test public void testDeserializationAsString01() throws Exception @@ -77,6 +85,48 @@ public void testDeserializationAsEmptyArrayEnabled() throws Throwable assertNull(value); } + /* + /********************************************************** + /* Tests for empty string handling + /********************************************************** + */ + + @Test + public void testLenientDeserializeFromEmptyString() throws Exception { + + String key = "yearMonth"; + ObjectMapper mapper = newMapper(); + ObjectReader objectReader = mapper.readerFor(MAP_TYPE_REF); + + String dateValAsEmptyStr = ""; + + String valueFromNullStr = mapper.writeValueAsString(asMap(key, null)); + Map actualMapFromNullStr = objectReader.readValue(valueFromNullStr); + YearMonth actualDateFromNullStr = actualMapFromNullStr.get(key); + assertNull(actualDateFromNullStr); + + String valueFromEmptyStr = mapper.writeValueAsString(asMap(key, dateValAsEmptyStr)); + Map actualMapFromEmptyStr = objectReader.readValue(valueFromEmptyStr); + YearMonth actualDateFromEmptyStr = actualMapFromEmptyStr.get(key); + assertEquals("empty string failed to deserialize to null with lenient setting",null, actualDateFromEmptyStr); + } + + @Test( expected = MismatchedInputException.class) + public void testStrictDeserializFromEmptyString() throws Exception { + + final String key = "YearMonth"; + final ObjectMapper mapper = mapperBuilder().build(); + mapper.configOverride(YearMonth.class) + .setFormat(JsonFormat.Value.forLeniency(false)); + final ObjectReader objectReader = mapper.readerFor(MAP_TYPE_REF); + + String valueFromNullStr = mapper.writeValueAsString(asMap(key, null)); + Map actualMapFromNullStr = objectReader.readValue(valueFromNullStr); + assertNull(actualMapFromNullStr.get(key)); + + String valueFromEmptyStr = mapper.writeValueAsString(asMap("date", "")); + objectReader.readValue(valueFromEmptyStr); + } private void expectFailure(String json) throws Throwable { try { From e9018698c63d28cbb680e24965c0fd623046450a Mon Sep 17 00:00:00 2001 From: michaelok Date: Wed, 16 Oct 2019 00:43:37 -0500 Subject: [PATCH 3/9] Instant deser should still return null for empty string if lenient, also added tests for empty string handling for Duration, Instant and ZonedDateTime deserializers. --- .../jsr310/deser/InstantDeserializer.java | 1 + .../jsr310/deser/DurationDeserTest.java | 6 ++ .../jsr310/deser/InstantDeserTest.java | 66 ++++++++++++++++--- .../jsr310/deser/ZonedDateTimeDeserTest.java | 50 ++++++++++++++ 4 files changed, 113 insertions(+), 10 deletions(-) diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializer.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializer.java index 772763b5..102761e3 100644 --- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializer.java +++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializer.java @@ -189,6 +189,7 @@ public T deserialize(JsonParser parser, DeserializationContext context) throws I if (!isLenient()) { return _failForNotLenient(parser, context, JsonToken.VALUE_STRING); } + return null; } // only check for other parsing modes if we are using default formatter if (_formatter == DateTimeFormatter.ISO_INSTANT || diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/DurationDeserTest.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/DurationDeserTest.java index 501bef24..aff2c184 100644 --- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/DurationDeserTest.java +++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/DurationDeserTest.java @@ -378,6 +378,12 @@ public void testDeserializationAsEmptyArrayEnabled() throws Throwable assertNull(value); } + /* + /********************************************************** + /* Tests for empty string handling + /********************************************************** + */ + @Test public void testLenientDeserializeFromEmptyString() throws Exception { diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserTest.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserTest.java index 75038482..97186b58 100644 --- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserTest.java +++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserTest.java @@ -1,19 +1,13 @@ package com.fasterxml.jackson.datatype.jsr310.deser; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import java.time.DateTimeException; -import java.time.Instant; -import java.time.LocalDate; -import java.time.ZoneId; -import java.time.ZoneOffset; -import java.time.ZonedDateTime; +import java.time.*; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; import java.time.temporal.Temporal; +import java.util.Map; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.exc.MismatchedInputException; import org.junit.Test; import com.fasterxml.jackson.annotation.JsonFormat; @@ -25,10 +19,14 @@ import com.fasterxml.jackson.datatype.jsr310.MockObjectConfiguration; import com.fasterxml.jackson.datatype.jsr310.ModuleTestBase; +import static org.junit.Assert.*; +import static org.junit.Assert.assertNull; + public class InstantDeserTest extends ModuleTestBase { private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ISO_INSTANT; private static final String CUSTOM_PATTERN = "yyyy-MM-dd HH:mm:ss"; + private final TypeReference> MAP_TYPE_REF = new TypeReference>() { }; static class Wrapper { @JsonFormat( @@ -479,4 +477,52 @@ public void testRoundTripOfInstantAndJavaUtilDate() throws Exception assertEquals(givenInstant, actual); } + + /* + /********************************************************** + /* Tests for empty string handling + /********************************************************** + */ + + @Test + public void testLenientDeserializeFromEmptyString() throws Exception { + + String key = "duration"; + ObjectMapper mapper = newMapper(); + ObjectReader objectReader = mapper.readerFor(MAP_TYPE_REF); + + String dateValAsNullStr = null; + String dateValAsEmptyStr = ""; + + String valueFromNullStr = mapper.writeValueAsString(asMap(key, dateValAsNullStr)); + Map actualMapFromNullStr = objectReader.readValue(valueFromNullStr); + Duration actualDateFromNullStr = actualMapFromNullStr.get(key); + assertNull(actualDateFromNullStr); + + String valueFromEmptyStr = mapper.writeValueAsString(asMap(key, dateValAsEmptyStr)); + Map actualMapFromEmptyStr = objectReader.readValue(valueFromEmptyStr); + Duration actualDateFromEmptyStr = actualMapFromEmptyStr.get(key); + assertEquals("empty string failed to deserialize to null with lenient setting", null, actualDateFromEmptyStr); + } + + @Test ( expected = MismatchedInputException.class) + public void testStrictDeserializeFromEmptyString() throws Exception { + + final String key = "duration"; + final ObjectMapper mapper = mapperBuilder().build(); + mapper.configOverride(Duration.class) + .setFormat(JsonFormat.Value.forLeniency(false)); + + final ObjectReader objectReader = mapper.readerFor(MAP_TYPE_REF); + final String dateValAsNullStr = null; + + // even with strict, null value should be deserialized without throwing an exception + String valueFromNullStr = mapper.writeValueAsString(asMap(key, dateValAsNullStr)); + Map actualMapFromNullStr = objectReader.readValue(valueFromNullStr); + assertNull(actualMapFromNullStr.get(key)); + + String dateValAsEmptyStr = ""; + String valueFromEmptyStr = mapper.writeValueAsString(asMap(key, dateValAsEmptyStr)); + objectReader.readValue(valueFromEmptyStr); + } } diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/ZonedDateTimeDeserTest.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/ZonedDateTimeDeserTest.java index c8a35bf9..a1009f2b 100644 --- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/ZonedDateTimeDeserTest.java +++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/ZonedDateTimeDeserTest.java @@ -1,17 +1,23 @@ package com.fasterxml.jackson.datatype.jsr310.deser; +import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectReader; +import com.fasterxml.jackson.databind.exc.MismatchedInputException; import com.fasterxml.jackson.datatype.jsr310.ModuleTestBase; import org.junit.Test; import java.io.IOException; +import java.time.Duration; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeParseException; +import java.util.Map; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; @@ -20,6 +26,7 @@ public class ZonedDateTimeDeserTest extends ModuleTestBase { private final ObjectReader READER = newMapper().readerFor(ZonedDateTime.class); + private final TypeReference> MAP_TYPE_REF = new TypeReference>() { }; @Test public void testDeserializationAsString01() throws Exception @@ -96,6 +103,49 @@ public void testDeserializationAsEmptyArrayEnabled() throws Throwable assertNull(value); } + /* + /********************************************************** + /* Tests for empty string handling + /********************************************************** + */ + + @Test + public void testLenientDeserializeFromEmptyString() throws Exception { + + String key = "zoneDateTime"; + ObjectMapper mapper = newMapper(); + ObjectReader objectReader = mapper.readerFor(MAP_TYPE_REF); + + String valueFromNullStr = mapper.writeValueAsString(asMap(key, null)); + Map actualMapFromNullStr = objectReader.readValue(valueFromNullStr); + ZonedDateTime actualDateFromNullStr = actualMapFromNullStr.get(key); + assertNull(actualDateFromNullStr); + + String valueFromEmptyStr = mapper.writeValueAsString(asMap(key, "")); + Map actualMapFromEmptyStr = objectReader.readValue(valueFromEmptyStr); + ZonedDateTime actualDateFromEmptyStr = actualMapFromEmptyStr.get(key); + assertEquals("empty string failed to deserialize to null with lenient setting", null, actualDateFromEmptyStr); + } + + @Test ( expected = MismatchedInputException.class) + public void testStrictDeserializeFromEmptyString() throws Exception { + + final String key = "ZonedDateTime"; + final ObjectMapper mapper = mapperBuilder().build(); + mapper.configOverride(ZonedDateTime.class) + .setFormat(JsonFormat.Value.forLeniency(false)); + + final ObjectReader objectReader = mapper.readerFor(MAP_TYPE_REF); + final String dateValAsNullStr = null; + + String valueFromNullStr = mapper.writeValueAsString(asMap(key, null)); + Map actualMapFromNullStr = objectReader.readValue(valueFromNullStr); + assertNull(actualMapFromNullStr.get(key)); + + String valueFromEmptyStr = mapper.writeValueAsString(asMap(key, "")); + objectReader.readValue(valueFromEmptyStr); + } + private void expectFailure(String json) throws Throwable { try { READER.readValue(aposToQuotes(json)); From 762c27de9c7442ded88da1ada7a6e6c49e982210 Mon Sep 17 00:00:00 2001 From: michaelok Date: Wed, 16 Oct 2019 22:43:22 -0500 Subject: [PATCH 4/9] Add strict/lenient and empty string check to Instant, add tests --- .../jsr310/deser/InstantDeserializer.java | 28 ++++++++++++++++--- .../deser/JSR310DateTimeDeserializerBase.java | 12 +++++++- .../jsr310/deser/InstantDeserTest.java | 13 ++++----- .../jsr310/deser/ZonedDateTimeDeserTest.java | 2 +- 4 files changed, 41 insertions(+), 14 deletions(-) diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializer.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializer.java index 102761e3..bec7068c 100644 --- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializer.java +++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializer.java @@ -151,6 +151,18 @@ protected InstantDeserializer(InstantDeserializer base, Boolean adjustToConte _adjustToContextTZOverride = adjustToContextTimezoneOverride; } + @SuppressWarnings("unchecked") + protected InstantDeserializer(InstantDeserializer base, DateTimeFormatter f, Boolean leniency) + { + super((Class) base.handledType(), f, leniency); + parsedToValue = base.parsedToValue; + fromMilliseconds = base.fromMilliseconds; + fromNanoseconds = base.fromNanoseconds; + adjust = base.adjust; + replaceZeroOffsetAsZ = (_formatter == DateTimeFormatter.ISO_INSTANT); + _adjustToContextTZOverride = base._adjustToContextTZOverride; + } + @Override protected InstantDeserializer withDateFormat(DateTimeFormatter dtf) { if (dtf == _formatter) { @@ -159,10 +171,12 @@ protected InstantDeserializer withDateFormat(DateTimeFormatter dtf) { return new InstantDeserializer(this, dtf); } - // !!! TODO: lenient vs strict? @Override protected InstantDeserializer withLeniency(Boolean leniency) { - return this; + if (_isLenient == !Boolean.FALSE.equals(leniency)) { + return this; + } + return new InstantDeserializer(this, _formatter, leniency); } @Override @@ -248,10 +262,16 @@ public JsonDeserializer createContextual(DeserializationContext ctxt, if (deserializer != this) { JsonFormat.Value val = findFormatOverrides(ctxt, property, handledType()); if (val != null) { - return new InstantDeserializer<>(deserializer, val.getFeature(JsonFormat.Feature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)); + deserializer = new InstantDeserializer<>(deserializer, val.getFeature(JsonFormat.Feature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)); + } + if (val.hasLenient()) { + Boolean leniency = val.getLenient(); + if (leniency != null) { + deserializer = deserializer.withLeniency(leniency); + } } } - return this; + return deserializer; } protected boolean shouldAdjustToContextTimezone(DeserializationContext context) { diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310DateTimeDeserializerBase.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310DateTimeDeserializerBase.java index 43f1f001..642b647d 100644 --- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310DateTimeDeserializerBase.java +++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310DateTimeDeserializerBase.java @@ -3,6 +3,7 @@ import java.io.IOException; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; +import java.time.temporal.Temporal; import java.util.Locale; import com.fasterxml.jackson.annotation.JsonFormat; @@ -60,6 +61,16 @@ protected JSR310DateTimeDeserializerBase(Class supportedType, DateTimeFormatt _shape = null; } + /** + * @since 2.11 + */ + public JSR310DateTimeDeserializerBase(Class supportedType, DateTimeFormatter f, Boolean leniency) { + super(supportedType); + _formatter = f; + _isLenient = !Boolean.FALSE.equals(leniency); + _shape = null; + } + /** * @since 2.10 */ @@ -93,7 +104,6 @@ protected JSR310DateTimeDeserializerBase(JSR310DateTimeDeserializerBase base, _isLenient = base._isLenient; } - protected abstract JSR310DateTimeDeserializerBase withDateFormat(DateTimeFormatter dtf); /** diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserTest.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserTest.java index 97186b58..5858a033 100644 --- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserTest.java +++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserTest.java @@ -508,21 +508,18 @@ public void testLenientDeserializeFromEmptyString() throws Exception { @Test ( expected = MismatchedInputException.class) public void testStrictDeserializeFromEmptyString() throws Exception { - final String key = "duration"; + final String key = "instant"; final ObjectMapper mapper = mapperBuilder().build(); - mapper.configOverride(Duration.class) + mapper.configOverride(Instant.class) .setFormat(JsonFormat.Value.forLeniency(false)); final ObjectReader objectReader = mapper.readerFor(MAP_TYPE_REF); - final String dateValAsNullStr = null; - // even with strict, null value should be deserialized without throwing an exception - String valueFromNullStr = mapper.writeValueAsString(asMap(key, dateValAsNullStr)); - Map actualMapFromNullStr = objectReader.readValue(valueFromNullStr); + String valueFromNullStr = mapper.writeValueAsString(asMap(key, null)); + Map actualMapFromNullStr = objectReader.readValue(valueFromNullStr); assertNull(actualMapFromNullStr.get(key)); - String dateValAsEmptyStr = ""; - String valueFromEmptyStr = mapper.writeValueAsString(asMap(key, dateValAsEmptyStr)); + String valueFromEmptyStr = mapper.writeValueAsString(asMap(key, "")); objectReader.readValue(valueFromEmptyStr); } } diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/ZonedDateTimeDeserTest.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/ZonedDateTimeDeserTest.java index a1009f2b..3a8ef8b2 100644 --- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/ZonedDateTimeDeserTest.java +++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/ZonedDateTimeDeserTest.java @@ -130,7 +130,7 @@ public void testLenientDeserializeFromEmptyString() throws Exception { @Test ( expected = MismatchedInputException.class) public void testStrictDeserializeFromEmptyString() throws Exception { - final String key = "ZonedDateTime"; + final String key = "zonedDateTime"; final ObjectMapper mapper = mapperBuilder().build(); mapper.configOverride(ZonedDateTime.class) .setFormat(JsonFormat.Value.forLeniency(false)); From 069099c3fbb688cdfc7a1138b7b34ab06983c4d7 Mon Sep 17 00:00:00 2001 From: michaelok Date: Wed, 16 Oct 2019 23:06:34 -0500 Subject: [PATCH 5/9] add null check for config in create context method. --- .../datatype/jsr310/deser/InstantDeserializer.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializer.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializer.java index bec7068c..519cb30f 100644 --- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializer.java +++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializer.java @@ -263,11 +263,11 @@ public JsonDeserializer createContextual(DeserializationContext ctxt, JsonFormat.Value val = findFormatOverrides(ctxt, property, handledType()); if (val != null) { deserializer = new InstantDeserializer<>(deserializer, val.getFeature(JsonFormat.Feature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)); - } - if (val.hasLenient()) { - Boolean leniency = val.getLenient(); - if (leniency != null) { - deserializer = deserializer.withLeniency(leniency); + if (val.hasLenient()) { + Boolean leniency = val.getLenient(); + if (leniency != null) { + deserializer = deserializer.withLeniency(leniency); + } } } } From e7be6f9ea6ac81e6aef81a86b4ed9916e0739aeb Mon Sep 17 00:00:00 2001 From: michaelok Date: Wed, 16 Oct 2019 23:16:12 -0500 Subject: [PATCH 6/9] cleanup imports --- .../datatype/jsr310/deser/DurationDeserializer.java | 12 +++++++----- .../jsr310/deser/JSR310DateTimeDeserializerBase.java | 1 - 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/DurationDeserializer.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/DurationDeserializer.java index 5cbebd7e..56f9426c 100644 --- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/DurationDeserializer.java +++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/DurationDeserializer.java @@ -20,7 +20,11 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.core.JsonTokenId; -import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.BeanProperty; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.deser.ContextualDeserializer; import com.fasterxml.jackson.datatype.jsr310.DecimalUtils; @@ -28,9 +32,7 @@ import java.math.BigDecimal; import java.time.DateTimeException; import java.time.Duration; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeFormatterBuilder; -import java.util.Locale; + /** * Deserializer for Java 8 temporal {@link Duration}s. @@ -52,7 +54,7 @@ private DurationDeserializer() /** * Since 2.11 */ - public DurationDeserializer(DurationDeserializer base, Boolean leniency) { + protected DurationDeserializer(DurationDeserializer base, Boolean leniency) { super(base, leniency); } diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310DateTimeDeserializerBase.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310DateTimeDeserializerBase.java index 642b647d..d4087f82 100644 --- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310DateTimeDeserializerBase.java +++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310DateTimeDeserializerBase.java @@ -3,7 +3,6 @@ import java.io.IOException; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; -import java.time.temporal.Temporal; import java.util.Locale; import com.fasterxml.jackson.annotation.JsonFormat; From 61b426e2e3216eb7c28d0be2aa80f9af0a40e6a1 Mon Sep 17 00:00:00 2001 From: michaelok Date: Thu, 17 Oct 2019 00:35:43 -0500 Subject: [PATCH 7/9] per code review, add test specifically from issue where default mapper is used --- .../JSR310StringParsableDeserializer.java | 41 +++++++++++++- .../jsr310/deser/LocalDateDeserTest.java | 2 +- .../jsr310/deser/LocalDateTimeDeserTest.java | 2 +- .../jsr310/deser/LocalTimeDeserTest.java | 2 +- .../jsr310/deser/MonthDayDeserTest.java | 2 +- .../jsr310/deser/PeriodDeserTest.java | 56 +++++++++++++++++-- .../jsr310/deser/YearMonthDeserTest.java | 2 +- .../jsr310/deser/ZoneIdDeserTest.java | 51 +++++++++++++++++ .../jsr310/deser/ZoneOffsetDeserTest.java | 48 ++++++++++++++++ 9 files changed, 195 insertions(+), 11 deletions(-) diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310StringParsableDeserializer.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310StringParsableDeserializer.java index 000b77ca..e0372eca 100644 --- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310StringParsableDeserializer.java +++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310StringParsableDeserializer.java @@ -22,11 +22,15 @@ import java.time.ZoneId; import java.time.ZoneOffset; +import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.BeanProperty; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.deser.ContextualDeserializer; import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; /** @@ -41,6 +45,7 @@ */ public class JSR310StringParsableDeserializer extends JSR310DeserializerBase + implements ContextualDeserializer { private static final long serialVersionUID = 1L; @@ -66,6 +71,14 @@ protected JSR310StringParsableDeserializer(Class supportedType, int valueId) _valueType = valueId; } + /** + * Since 2.11 + */ + protected JSR310StringParsableDeserializer(JSR310StringParsableDeserializer base, Boolean leniency) { + super(base, leniency); + _valueType = base._valueType; + } + @SuppressWarnings("unchecked") protected static JsonDeserializer createDeserializer(Class type, int typeId) { return (JsonDeserializer) new JSR310StringParsableDeserializer(type, typeId); @@ -73,8 +86,12 @@ protected static JsonDeserializer createDeserializer(Class type, int t @Override protected JSR310StringParsableDeserializer withLeniency(Boolean leniency) { - // TODO: implement! - return null; + if (_isLenient == !Boolean.FALSE.equals(leniency)) { + return this; + } + // TODO: or should this be casting as above in createDeserializer? But then in createContext, we need to + // call the withLeniency method in this class. (See if we can follow InstantDeser convention here?) + return new JSR310StringParsableDeserializer(this, leniency); } @Override @@ -83,6 +100,9 @@ public Object deserialize(JsonParser parser, DeserializationContext context) thr if (parser.hasToken(JsonToken.VALUE_STRING)) { String string = parser.getText().trim(); if (string.length() == 0) { + if (!isLenient()) { + return _failForNotLenient(parser, context, JsonToken.VALUE_STRING); + } return null; } try { @@ -124,4 +144,21 @@ public Object deserializeWithType(JsonParser parser, DeserializationContext cont } return deserializer.deserializeTypedFromAny(parser, context); } + + @Override + public JsonDeserializer createContextual(DeserializationContext ctxt, + BeanProperty property) throws JsonMappingException + { + JsonFormat.Value format = findFormatOverrides(ctxt, property, handledType()); + JSR310StringParsableDeserializer deser = this; + if (format != null) { + if (format.hasLenient()) { + Boolean leniency = format.getLenient(); + if (leniency != null) { + deser = this.withLeniency(leniency); + } + } + } + return deser; + } } 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 4441cae9..0637ffa8 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 @@ -216,7 +216,7 @@ public void testLenientDeserializeFromEmptyString() throws Exception { } @Test( expected = MismatchedInputException.class) - public void testStrictDeserializFromEmptyString() throws Exception { + public void testStrictDeserializeFromEmptyString() throws Exception { final String key = "date"; final ObjectMapper mapper = mapperBuilder().build(); diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/LocalDateTimeDeserTest.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/LocalDateTimeDeserTest.java index 08811838..ea8b7b5d 100644 --- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/LocalDateTimeDeserTest.java +++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/LocalDateTimeDeserTest.java @@ -208,7 +208,7 @@ public void testLenientDeserializeFromEmptyString() throws Exception { } @Test( expected = MismatchedInputException.class) - public void testStrictDeserializFromEmptyString() throws Exception { + public void testStrictDeserializeFromEmptyString() throws Exception { final String key = "datetime"; final ObjectMapper mapper = mapperBuilder().build(); diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/LocalTimeDeserTest.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/LocalTimeDeserTest.java index 69e30d4e..26c6b6a7 100644 --- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/LocalTimeDeserTest.java +++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/LocalTimeDeserTest.java @@ -242,7 +242,7 @@ public void testLenientDeserializeFromEmptyString() throws Exception { } @Test( expected = MismatchedInputException.class) - public void testStrictDeserializFromEmptyString() throws Exception { + public void testStrictDeserializeFromEmptyString() throws Exception { final String key = "localTime"; final ObjectMapper mapper = mapperBuilder().build(); diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/MonthDayDeserTest.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/MonthDayDeserTest.java index 50add4f8..1aebcc38 100644 --- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/MonthDayDeserTest.java +++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/MonthDayDeserTest.java @@ -163,7 +163,7 @@ public void testFormatAnnotationArray() throws Exception */ @Test( expected = MismatchedInputException.class) - public void testStrictDeserializFromEmptyString() throws Exception { + public void testStrictDeserializeFromEmptyString() throws Exception { final String key = "monthDay"; final ObjectMapper mapper = mapperBuilder().build(); diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/PeriodDeserTest.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/PeriodDeserTest.java index a6d8ed11..da4cb10a 100644 --- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/PeriodDeserTest.java +++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/PeriodDeserTest.java @@ -16,22 +16,29 @@ package com.fasterxml.jackson.datatype.jsr310.deser; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - +import java.time.Instant; import java.time.Period; +import java.time.YearMonth; import java.time.temporal.TemporalAmount; +import java.util.Map; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; +import com.fasterxml.jackson.databind.exc.MismatchedInputException; import com.fasterxml.jackson.datatype.jsr310.MockObjectConfiguration; import com.fasterxml.jackson.datatype.jsr310.ModuleTestBase; import org.junit.Test; +import static org.junit.Assert.*; +import static org.junit.Assert.assertNull; + public class PeriodDeserTest extends ModuleTestBase { private final ObjectMapper MAPPER = newMapper(); + private final TypeReference> MAP_TYPE_REF = new TypeReference>() { }; @Test public void testDeserialization01() throws Exception @@ -65,4 +72,45 @@ public void testDeserializationWithTypeInfo01() throws Exception assertTrue("The value should be a Period.", value instanceof Period); assertEquals("The value is not correct.", period, value); } + + /* + /********************************************************** + /* Tests for empty string handling + /********************************************************** + */ + + @Test + public void testLenientDeserializeFromEmptyString() throws Exception { + + String key = "period"; + ObjectMapper mapper = newMapper(); + ObjectReader objectReader = mapper.readerFor(MAP_TYPE_REF); + + String valueFromNullStr = mapper.writeValueAsString(asMap(key, null)); + Map actualMapFromNullStr = objectReader.readValue(valueFromNullStr); + Period actualDateFromNullStr = actualMapFromNullStr.get(key); + assertNull(actualDateFromNullStr); + + String valueFromEmptyStr = mapper.writeValueAsString(asMap(key, "")); + Map actualMapFromEmptyStr = objectReader.readValue(valueFromEmptyStr); + Period actualDateFromEmptyStr = actualMapFromEmptyStr.get(key); + assertEquals("empty string failed to deserialize to null with lenient setting",null, actualDateFromEmptyStr); + } + + @Test( expected = MismatchedInputException.class) + public void testStrictDeserializeFromEmptyString() throws Exception { + + final String key = "period"; + final ObjectMapper mapper = mapperBuilder().build(); + mapper.configOverride(Period.class) + .setFormat(JsonFormat.Value.forLeniency(false)); + final ObjectReader objectReader = mapper.readerFor(MAP_TYPE_REF); + + String valueFromNullStr = mapper.writeValueAsString(asMap(key, null)); + Map actualMapFromNullStr = objectReader.readValue(valueFromNullStr); + assertNull(actualMapFromNullStr.get(key)); + + String valueFromEmptyStr = mapper.writeValueAsString(asMap("date", "")); + objectReader.readValue(valueFromEmptyStr); + } } diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/YearMonthDeserTest.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/YearMonthDeserTest.java index a4f32e12..2fe9d739 100644 --- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/YearMonthDeserTest.java +++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/YearMonthDeserTest.java @@ -112,7 +112,7 @@ public void testLenientDeserializeFromEmptyString() throws Exception { } @Test( expected = MismatchedInputException.class) - public void testStrictDeserializFromEmptyString() throws Exception { + public void testStrictDeserializeFromEmptyString() throws Exception { final String key = "YearMonth"; final ObjectMapper mapper = mapperBuilder().build(); diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/ZoneIdDeserTest.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/ZoneIdDeserTest.java index 7f7696f4..424f48d8 100644 --- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/ZoneIdDeserTest.java +++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/ZoneIdDeserTest.java @@ -17,10 +17,17 @@ package com.fasterxml.jackson.datatype.jsr310.deser; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Map; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; +import com.fasterxml.jackson.databind.exc.MismatchedInputException; import com.fasterxml.jackson.datatype.jsr310.MockObjectConfiguration; import com.fasterxml.jackson.datatype.jsr310.ModuleTestBase; @@ -29,6 +36,7 @@ public class ZoneIdDeserTest extends ModuleTestBase { private ObjectMapper MAPPER = newMapper(); + private final TypeReference> MAP_TYPE_REF = new TypeReference>() { }; private final ObjectMapper MOCK_OBJECT_MIXIN_MAPPER = mapperBuilder() .addMixIn(ZoneId.class, MockObjectConfiguration.class) @@ -54,4 +62,47 @@ public void testDeserializationWithTypeInfo02() throws Exception ZoneId value = MOCK_OBJECT_MIXIN_MAPPER.readValue("[\"" + ZoneId.class.getName() + "\",\"America/Denver\"]", ZoneId.class); assertEquals("The value is not correct.", ZoneId.of("America/Denver"), value); } + + /* + /********************************************************** + /* Tests for empty string handling + /********************************************************** + */ + + @Test + public void testLenientDeserializeFromEmptyString() throws Exception { + + String key = "zoneId"; + ObjectMapper mapper = newMapper(); + ObjectReader objectReader = mapper.readerFor(MAP_TYPE_REF); + + String valueFromNullStr = mapper.writeValueAsString(asMap(key, null)); + Map actualMapFromNullStr = objectReader.readValue(valueFromNullStr); + ZoneId actualDateFromNullStr = actualMapFromNullStr.get(key); + assertNull(actualDateFromNullStr); + + String valueFromEmptyStr = mapper.writeValueAsString(asMap(key, "")); + Map actualMapFromEmptyStr = objectReader.readValue(valueFromEmptyStr); + ZoneId actualDateFromEmptyStr = actualMapFromEmptyStr.get(key); + assertEquals("empty string failed to deserialize to null with lenient setting", null, actualDateFromEmptyStr); + } + + @Test ( expected = MismatchedInputException.class) + public void testStrictDeserializeFromEmptyString() throws Exception { + + final String key = "zoneId"; + final ObjectMapper mapper = mapperBuilder().build(); + mapper.configOverride(ZoneId.class) + .setFormat(JsonFormat.Value.forLeniency(false)); + + final ObjectReader objectReader = mapper.readerFor(MAP_TYPE_REF); + final String dateValAsNullStr = null; + + String valueFromNullStr = mapper.writeValueAsString(asMap(key, null)); + Map actualMapFromNullStr = objectReader.readValue(valueFromNullStr); + assertNull(actualMapFromNullStr.get(key)); + + String valueFromEmptyStr = mapper.writeValueAsString(asMap(key, "")); + objectReader.readValue(valueFromEmptyStr); + } } diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/ZoneOffsetDeserTest.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/ZoneOffsetDeserTest.java index 8c151d1c..120d9b67 100644 --- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/ZoneOffsetDeserTest.java +++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/ZoneOffsetDeserTest.java @@ -18,12 +18,15 @@ import java.time.ZoneId; import java.time.ZoneOffset; +import java.util.Map; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -38,6 +41,7 @@ public class ZoneOffsetDeserTest extends ModuleTestBase { private final static ObjectMapper MAPPER = newMapper(); private final static ObjectReader READER = MAPPER.readerFor(ZoneOffset.class); + private final TypeReference> MAP_TYPE_REF = new TypeReference>() { }; @Test public void testDeserializationFromString() throws Exception @@ -121,4 +125,48 @@ public void testDeserializationAsEmptyArrayEnabled() throws Throwable .readValue("[]"); assertNull(value); } + + /* + /********************************************************** + /* Tests for empty string handling + /********************************************************** + */ + + @Test + public void testLenientDeserializeFromEmptyString() throws Exception { + + String key = "zoneOffset"; + ObjectMapper mapper = newMapper(); + ObjectReader objectReader = mapper.readerFor(MAP_TYPE_REF); + + String valueFromNullStr = mapper.writeValueAsString(asMap(key, null)); + Map actualMapFromNullStr = objectReader.readValue(valueFromNullStr); + ZoneId actualDateFromNullStr = actualMapFromNullStr.get(key); + assertNull(actualDateFromNullStr); + + String valueFromEmptyStr = mapper.writeValueAsString(asMap(key, "")); + Map actualMapFromEmptyStr = objectReader.readValue(valueFromEmptyStr); + ZoneId actualDateFromEmptyStr = actualMapFromEmptyStr.get(key); + assertEquals("empty string failed to deserialize to null with lenient setting", null, actualDateFromEmptyStr); + } + + @Test ( expected = MismatchedInputException.class) + public void testStrictDeserializeFromEmptyString() throws Exception { + + final String key = "zoneOffset"; + final ObjectMapper mapper = mapperBuilder().build(); + mapper.configOverride(ZoneOffset.class) + .setFormat(JsonFormat.Value.forLeniency(false)); + + final ObjectReader objectReader = mapper.readerFor(MAP_TYPE_REF); + final String dateValAsNullStr = null; + + String valueFromNullStr = mapper.writeValueAsString(asMap(key, null)); + Map actualMapFromNullStr = objectReader.readValue(valueFromNullStr); + assertNull(actualMapFromNullStr.get(key)); + + String valueFromEmptyStr = mapper.writeValueAsString(asMap(key, "")); + objectReader.readValue(valueFromEmptyStr); + } + } From e11305a83ae4825e1f848d7a7411bd3f0d5c1ff3 Mon Sep 17 00:00:00 2001 From: michaelok Date: Thu, 17 Oct 2019 01:04:03 -0500 Subject: [PATCH 8/9] Add tests for empty string lenient/strict handling --- .../jsr310/deser/OffsetDateTimeDeserTest.java | 60 ++++++++++++++++--- .../jsr310/deser/OffsetTimeDeserTest.java | 52 ++++++++++++++++ .../datatype/jsr310/deser/YearDeserTest.java | 31 +++++++++- .../jsr310/deser/ZoneOffsetDeserTest.java | 2 +- 4 files changed, 134 insertions(+), 11 deletions(-) diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/OffsetDateTimeDeserTest.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/OffsetDateTimeDeserTest.java index e7e45a56..b35b7de7 100644 --- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/OffsetDateTimeDeserTest.java +++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/OffsetDateTimeDeserTest.java @@ -1,35 +1,34 @@ package com.fasterxml.jackson.datatype.jsr310.deser; -import java.time.Instant; -import java.time.LocalDate; -import java.time.OffsetDateTime; -import java.time.ZoneId; -import java.time.ZoneOffset; +import java.time.*; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoField; import java.time.temporal.ChronoUnit; import java.time.temporal.Temporal; +import java.util.Map; import java.util.TimeZone; import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.exc.MismatchedInputException; import com.fasterxml.jackson.datatype.jsr310.DecimalUtils; import com.fasterxml.jackson.datatype.jsr310.MockObjectConfiguration; import com.fasterxml.jackson.datatype.jsr310.ModuleTestBase; import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; +import static org.junit.Assert.assertNull; public class OffsetDateTimeDeserTest extends ModuleTestBase { private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ISO_OFFSET_DATE_TIME; - + private final TypeReference> MAP_TYPE_REF = new TypeReference>() { }; private static final ZoneId UTC = ZoneId.of("UTC"); private static final ZoneId Z1 = ZoneId.of("America/Chicago"); @@ -615,6 +614,49 @@ public void testRoundTripOfOffsetDateTimeAndJavaUtilDate() throws Exception assertEquals(givenInstant.atOffset(ZoneOffset.UTC), actual); } + /* + /********************************************************** + /* Tests for empty string handling + /********************************************************** + */ + + @Test + public void testLenientDeserializeFromEmptyString() throws Exception { + + String key = "OffsetDateTime"; + ObjectMapper mapper = newMapper(); + ObjectReader objectReader = mapper.readerFor(MAP_TYPE_REF); + + String valueFromNullStr = mapper.writeValueAsString(asMap(key, null)); + Map actualMapFromNullStr = objectReader.readValue(valueFromNullStr); + OffsetDateTime actualDateFromNullStr = actualMapFromNullStr.get(key); + assertNull(actualDateFromNullStr); + + String valueFromEmptyStr = mapper.writeValueAsString(asMap(key, "")); + Map actualMapFromEmptyStr = objectReader.readValue(valueFromEmptyStr); + OffsetDateTime actualDateFromEmptyStr = actualMapFromEmptyStr.get(key); + assertEquals("empty string failed to deserialize to null with lenient setting", null, actualDateFromEmptyStr); + } + + @Test ( expected = MismatchedInputException.class) + public void testStrictDeserializeFromEmptyString() throws Exception { + + final String key = "OffsetDateTime"; + final ObjectMapper mapper = mapperBuilder().build(); + mapper.configOverride(OffsetDateTime.class) + .setFormat(JsonFormat.Value.forLeniency(false)); + + final ObjectReader objectReader = mapper.readerFor(MAP_TYPE_REF); + final String dateValAsNullStr = null; + + String valueFromNullStr = mapper.writeValueAsString(asMap(key, null)); + Map actualMapFromNullStr = objectReader.readValue(valueFromNullStr); + assertNull(actualMapFromNullStr.get(key)); + + String valueFromEmptyStr = mapper.writeValueAsString(asMap(key, "")); + objectReader.readValue(valueFromEmptyStr); + } + private static void assertIsEqual(OffsetDateTime expected, OffsetDateTime actual) { assertTrue("The value is not correct. Expected timezone-adjusted <" + expected + ">, actual <" + actual + ">.", diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/OffsetTimeDeserTest.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/OffsetTimeDeserTest.java index a5db19c0..10aff9bf 100644 --- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/OffsetTimeDeserTest.java +++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/OffsetTimeDeserTest.java @@ -1,10 +1,13 @@ package com.fasterxml.jackson.datatype.jsr310.deser; +import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectReader; +import com.fasterxml.jackson.databind.exc.MismatchedInputException; import com.fasterxml.jackson.datatype.jsr310.MockObjectConfiguration; import com.fasterxml.jackson.datatype.jsr310.ModuleTestBase; @@ -12,10 +15,13 @@ import java.io.IOException; import java.time.OffsetTime; +import java.time.Year; +import java.time.ZoneId; import java.time.ZoneOffset; import java.time.format.DateTimeParseException; import java.time.temporal.Temporal; import java.util.List; +import java.util.Map; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -25,6 +31,9 @@ public class OffsetTimeDeserTest extends ModuleTestBase { + + private final TypeReference> MAP_TYPE_REF = new TypeReference>() { }; + // for [datatype-jsr310#45] static class Pojo45s { public String name; @@ -237,6 +246,49 @@ public void testDeserializationAsEmptyArrayEnabled() throws Throwable assertNull(value); } + /* + /********************************************************** + /* Tests for empty string handling + /********************************************************** + */ + + @Test + public void testLenientDeserializeFromEmptyString() throws Exception { + + String key = "OffsetTime"; + ObjectMapper mapper = newMapper(); + ObjectReader objectReader = mapper.readerFor(MAP_TYPE_REF); + + String valueFromNullStr = mapper.writeValueAsString(asMap(key, null)); + Map actualMapFromNullStr = objectReader.readValue(valueFromNullStr); + OffsetTime actualDateFromNullStr = actualMapFromNullStr.get(key); + assertNull(actualDateFromNullStr); + + String valueFromEmptyStr = mapper.writeValueAsString(asMap(key, "")); + Map actualMapFromEmptyStr = objectReader.readValue(valueFromEmptyStr); + OffsetTime actualDateFromEmptyStr = actualMapFromEmptyStr.get(key); + assertEquals("empty string failed to deserialize to null with lenient setting", null, actualDateFromEmptyStr); + } + + @Test ( expected = MismatchedInputException.class) + public void testStrictDeserializeFromEmptyString() throws Exception { + + final String key = "OffsetTime"; + final ObjectMapper mapper = mapperBuilder().build(); + mapper.configOverride(OffsetTime.class) + .setFormat(JsonFormat.Value.forLeniency(false)); + + final ObjectReader objectReader = mapper.readerFor(MAP_TYPE_REF); + final String dateValAsNullStr = null; + + String valueFromNullStr = mapper.writeValueAsString(asMap(key, null)); + Map actualMapFromNullStr = objectReader.readValue(valueFromNullStr); + assertNull(actualMapFromNullStr.get(key)); + + String valueFromEmptyStr = mapper.writeValueAsString(asMap(key, "")); + objectReader.readValue(valueFromEmptyStr); + } + private void expectFailure(String json) throws Throwable { try { read(json); diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/YearDeserTest.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/YearDeserTest.java index 9824d808..767dfff0 100644 --- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/YearDeserTest.java +++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/YearDeserTest.java @@ -18,16 +18,20 @@ import java.io.IOException; import java.time.Year; +import java.time.YearMonth; import java.time.format.DateTimeParseException; import java.time.temporal.Temporal; +import java.util.Map; import java.util.Objects; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectReader; +import com.fasterxml.jackson.databind.exc.MismatchedInputException; import com.fasterxml.jackson.datatype.jsr310.MockObjectConfiguration; import com.fasterxml.jackson.datatype.jsr310.ModuleTestBase; @@ -40,6 +44,9 @@ public class YearDeserTest extends ModuleTestBase { + + private final TypeReference> MAP_TYPE_REF = new TypeReference>() { }; + static class FormattedYear { @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "'Y'yyyy") public Year value; @@ -203,7 +210,29 @@ public void testWithCustomFormat78() throws Exception ObjectTest result = MAPPER.readValue(json, ObjectTest.class); assertEquals(input, result); } - + + /* + /********************************************************** + /* Tests for empty string handling + /********************************************************** + */ + + @Test( expected = MismatchedInputException.class) + public void testStrictDeserializeFromEmptyString() throws Exception { + + final String key = "Year"; + final ObjectMapper mapper = mapperBuilder().build(); + // YearDeserializer is always strict as far as empty strings, so lenient/strict has no effect + final ObjectReader objectReader = mapper.readerFor(MAP_TYPE_REF); + + String valueFromNullStr = mapper.writeValueAsString(asMap(key, null)); + Map actualMapFromNullStr = objectReader.readValue(valueFromNullStr); + assertNull(actualMapFromNullStr.get(key)); + + String valueFromEmptyStr = mapper.writeValueAsString(asMap("date", "")); + objectReader.readValue(valueFromEmptyStr); + } + /* /********************************************************** /* Helper methods diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/ZoneOffsetDeserTest.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/ZoneOffsetDeserTest.java index 120d9b67..7ac04863 100644 --- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/ZoneOffsetDeserTest.java +++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/ZoneOffsetDeserTest.java @@ -126,7 +126,7 @@ public void testDeserializationAsEmptyArrayEnabled() throws Throwable assertNull(value); } - /* + /* /********************************************************** /* Tests for empty string handling /********************************************************** From 8ec1e5a98630214382382982923dfed7795da67f Mon Sep 17 00:00:00 2001 From: michaelok Date: Thu, 17 Oct 2019 01:05:33 -0500 Subject: [PATCH 9/9] Cleanup formatting. --- .../jackson/datatype/jsr310/deser/OffsetDateTimeDeserTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/OffsetDateTimeDeserTest.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/OffsetDateTimeDeserTest.java index b35b7de7..3752f2ed 100644 --- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/OffsetDateTimeDeserTest.java +++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/OffsetDateTimeDeserTest.java @@ -614,7 +614,7 @@ public void testRoundTripOfOffsetDateTimeAndJavaUtilDate() throws Exception assertEquals(givenInstant.atOffset(ZoneOffset.UTC), actual); } - /* + /* /********************************************************** /* Tests for empty string handling /**********************************************************