From f06319346134b3c19a173e63c8e68a281e881386 Mon Sep 17 00:00:00 2001 From: Mike Date: Fri, 18 Oct 2019 12:24:21 -0500 Subject: [PATCH] Prevent deserialization of "" as `null` for `Duration`, `Instant`, `LocalTime`, `OffsetTime`, `Period`, `ZoneDateTime`, `ZoneId`, `ZoneOffset` and `YearMonth` in "strict" (non-lenient) (#144) Fix #138: Prevent deserialization of "" as `null` for `Duration`, `Instant`, `LocalTime`, `OffsetTime`, and `YearMonth` in "strict" (non-lenient) mode --- .../jsr310/deser/DurationDeserializer.java | 40 +++++++++++- .../jsr310/deser/InstantDeserializer.java | 31 +++++++-- .../deser/JSR310DateTimeDeserializerBase.java | 11 +++- .../jsr310/deser/JSR310DeserializerBase.java | 58 ++++++++++++++++- .../JSR310StringParsableDeserializer.java | 43 +++++++++++++ .../jsr310/deser/LocalTimeDeserializer.java | 13 +++- .../jsr310/deser/OffsetTimeDeserializer.java | 13 +++- .../jsr310/deser/YearMonthDeserializer.java | 13 +++- .../jsr310/deser/DurationDeserTest.java | 54 ++++++++++++++++ .../jsr310/deser/InstantDeserTest.java | 63 ++++++++++++++++--- .../jsr310/deser/LocalDateDeserTest.java | 2 +- .../jsr310/deser/LocalDateTimeDeserTest.java | 2 +- .../jsr310/deser/LocalTimeDeserTest.java | 48 ++++++++++++++ .../jsr310/deser/MonthDayDeserTest.java | 27 ++++++++ .../jsr310/deser/OffsetDateTimeDeserTest.java | 60 +++++++++++++++--- .../jsr310/deser/OffsetTimeDeserTest.java | 52 +++++++++++++++ .../jsr310/deser/PeriodDeserTest.java | 56 +++++++++++++++-- .../datatype/jsr310/deser/YearDeserTest.java | 31 ++++++++- .../jsr310/deser/YearMonthDeserTest.java | 50 +++++++++++++++ .../jsr310/deser/ZoneIdDeserTest.java | 51 +++++++++++++++ .../jsr310/deser/ZoneOffsetDeserTest.java | 48 ++++++++++++++ .../jsr310/deser/ZonedDateTimeDeserTest.java | 50 +++++++++++++++ 22 files changed, 775 insertions(+), 41 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..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 @@ -16,11 +16,16 @@ 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.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; import java.io.IOException; @@ -28,13 +33,14 @@ import java.time.DateTimeException; import java.time.Duration; + /** * Deserializer for Java 8 temporal {@link Duration}s. * * @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 +51,18 @@ private DurationDeserializer() super(Duration.class); } + /** + * Since 2.11 + */ + protected 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 +81,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 +102,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..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 @@ -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 @@ -186,6 +200,9 @@ public T deserialize(JsonParser parser, DeserializationContext context) throws I { String string = parser.getText().trim(); if (string.length() == 0) { + if (!isLenient()) { + return _failForNotLenient(parser, context, JsonToken.VALUE_STRING); + } return null; } // only check for other parsing modes if we are using default formatter @@ -245,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..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 @@ -60,6 +60,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 +103,6 @@ protected JSR310DateTimeDeserializerBase(JSR310DateTimeDeserializerBase base, _isLenient = base._isLenient; } - protected abstract JSR310DateTimeDeserializerBase withDateFormat(DateTimeFormatter dtf); /** 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..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,17 +71,38 @@ 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); } + @Override + protected JSR310StringParsableDeserializer withLeniency(Boolean leniency) { + 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 public Object deserialize(JsonParser parser, DeserializationContext context) throws IOException { 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 { @@ -118,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/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/DurationDeserTest.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/DurationDeserTest.java index 68d2191a..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 @@ -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,52 @@ public void testDeserializationAsEmptyArrayEnabled() throws Throwable .readerFor(Duration.class).readValue(aposToQuotes(json)); assertNull(value); } + + /* + /********************************************************** + /* 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/InstantDeserTest.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserTest.java index 75038482..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 @@ -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,49 @@ 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 = "instant"; + final ObjectMapper mapper = mapperBuilder().build(); + mapper.configOverride(Instant.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(key, "")); + objectReader.readValue(valueFromEmptyStr); + } } 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 072e8fde..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 @@ -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 testStrictDeserializeFromEmptyString() 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..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 @@ -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 testStrictDeserializeFromEmptyString() 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/OffsetDateTimeDeserTest.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/OffsetDateTimeDeserTest.java index e7e45a56..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 @@ -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/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/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/YearMonthDeserTest.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/YearMonthDeserTest.java index 9034a0d6..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 @@ -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 testStrictDeserializeFromEmptyString() 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 { 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..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 @@ -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); + } + } 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..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 @@ -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));