diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/NullsFailProvider.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/NullsFailProvider.java index ffb7564f78..ba870c7da1 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/NullsFailProvider.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/NullsFailProvider.java @@ -17,11 +17,19 @@ public class NullsFailProvider protected final PropertyName _name; protected final JavaType _type; - public NullsFailProvider(PropertyName name, JavaType type) { + protected NullsFailProvider(PropertyName name, JavaType type) { _name = name; _type = type; } + public static NullsFailProvider constructForProperty(BeanProperty prop) { + return new NullsFailProvider(prop.getFullName(), prop.getType()); + } + + public static NullsFailProvider constructForRootValue(JavaType t) { + return new NullsFailProvider(null, t); + } + @Override public AccessPattern getNullAccessPattern() { // Must be called every time to effect the exception... diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/PrimitiveArrayDeserializers.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/PrimitiveArrayDeserializers.java index ea5a3e28da..6c817cefb8 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/PrimitiveArrayDeserializers.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/PrimitiveArrayDeserializers.java @@ -10,6 +10,10 @@ import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.annotation.JacksonStdImpl; import com.fasterxml.jackson.databind.deser.ContextualDeserializer; +import com.fasterxml.jackson.databind.deser.NullValueProvider; +import com.fasterxml.jackson.databind.deser.impl.NullsConstantProvider; +import com.fasterxml.jackson.databind.deser.impl.NullsFailProvider; +import com.fasterxml.jackson.databind.exc.InvalidNullException; import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; import com.fasterxml.jackson.databind.util.AccessPattern; import com.fasterxml.jackson.databind.util.ArrayBuilders; @@ -34,22 +38,32 @@ public abstract class PrimitiveArrayDeserializers extends StdDeserializer // since 2.9 private transient Object _emptyValue; - protected final boolean _skipNullValues; + /** + * Flag that indicates need for special handling; either failing + * (throw exception) or skipping + */ + protected final NullValueProvider _nuller; + + /* + /******************************************************** + /* Life-cycle + /******************************************************** + */ protected PrimitiveArrayDeserializers(Class cls) { super(cls); _unwrapSingle = null; - _skipNullValues = false; + _nuller = null; } /** * @since 2.7 */ protected PrimitiveArrayDeserializers(PrimitiveArrayDeserializers base, - Nulls nullStyle, Boolean unwrapSingle) { + NullValueProvider nuller, Boolean unwrapSingle) { super(base._valueClass); _unwrapSingle = unwrapSingle; - _skipNullValues = (nullStyle == Nulls.SKIP); + _nuller = nuller; } public static JsonDeserializer forType(Class rawType) @@ -83,6 +97,59 @@ public static JsonDeserializer forType(Class rawType) throw new IllegalStateException(); } + @Override + public JsonDeserializer createContextual(DeserializationContext ctxt, + BeanProperty property) throws JsonMappingException + { + Boolean unwrapSingle = findFormatFeature(ctxt, property, _valueClass, + JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY); + NullValueProvider nuller = null; + + Nulls nullStyle = findContentNullStyle(ctxt, property); + if (nullStyle == Nulls.SKIP) { + nuller = NullsConstantProvider.skipper(); + } else if (nullStyle == Nulls.FAIL) { + if (property == null) { + nuller = NullsFailProvider.constructForRootValue(ctxt.constructType(_valueClass)); + } else { + nuller = NullsFailProvider.constructForProperty(property); + } + } + if ((unwrapSingle == _unwrapSingle) && (nuller == _nuller)) { + return this; + } + return withResolved(nuller, unwrapSingle); + } + + /* + /******************************************************** + /* Abstract methods for sub-classes to implement + /******************************************************** + */ + + /** + * @since 2.9 + */ + protected abstract T _concat(T oldValue, T newValue); + + protected abstract T handleSingleElementUnwrapped(JsonParser p, + DeserializationContext ctxt) throws IOException; + + /** + * @since 2.9 + */ + protected abstract PrimitiveArrayDeserializers withResolved(NullValueProvider nuller, + Boolean unwrapSingle); + + // since 2.9 + protected abstract T _constructEmpty(); + + /* + /******************************************************** + /* Default implementations + /******************************************************** + */ + @Override // since 2.9 public Boolean supportsUpdate(DeserializationConfig config) { return Boolean.TRUE; @@ -102,31 +169,6 @@ public Object getEmptyValue(DeserializationContext ctxt) throws JsonMappingExcep } return empty; } - - /** - * @since 2.9 - */ - protected abstract PrimitiveArrayDeserializers withResolved(Nulls nullStyle, - Boolean unwrapSingle); - - // since 2.9 - protected abstract T _constructEmpty(); - - @Override - public JsonDeserializer createContextual(DeserializationContext ctxt, - BeanProperty property) throws JsonMappingException - { - Boolean unwrapSingle = findFormatFeature(ctxt, property, _valueClass, - JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY); - Nulls nullStyle = findContentNullStyle(ctxt, property); - if (nullStyle == Nulls.SET) { - nullStyle = null; - } - if ((unwrapSingle == _unwrapSingle) && (nullStyle == null)) { - return this; - } - return withResolved(nullStyle, unwrapSingle); - } @Override public Object deserializeWithType(JsonParser p, DeserializationContext ctxt, @@ -151,11 +193,12 @@ public T deserialize(JsonParser p, DeserializationContext ctxt, T existing) thro return _concat(existing, newValue); } - /** - * @since 2.9 + /* + /******************************************************** + /* Helper methods for sub-classes + /******************************************************** */ - protected abstract T _concat(T oldValue, T newValue); - + /* * Convenience method that constructs a concatenation of two arrays, * with the type they have. @@ -197,8 +240,10 @@ protected T handleNonArray(JsonParser p, DeserializationContext ctxt) throws IOE return (T) ctxt.handleUnexpectedToken(_valueClass, p); } - protected abstract T handleSingleElementUnwrapped(JsonParser p, - DeserializationContext ctxt) throws IOException; + protected void _failOnNull(DeserializationContext ctxt) throws IOException + { + throw InvalidNullException.from(ctxt, null, ctxt.constructType(_valueClass)); + } /* /******************************************************** @@ -213,12 +258,12 @@ final static class CharDeser private static final long serialVersionUID = 1L; public CharDeser() { super(char[].class); } - protected CharDeser(CharDeser base, Nulls nullStyle,Boolean unwrapSingle) { - super(base, nullStyle, unwrapSingle); + protected CharDeser(CharDeser base, NullValueProvider nuller, Boolean unwrapSingle) { + super(base, nuller, unwrapSingle); } @Override - protected PrimitiveArrayDeserializers withResolved(Nulls nullStyle, + protected PrimitiveArrayDeserializers withResolved(NullValueProvider nuller, Boolean unwrapSingle) { // 11-Dec-2015, tatu: Not sure how re-wrapping would work; omit return this; @@ -254,7 +299,8 @@ public char[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOEx String str; if (t == JsonToken.VALUE_STRING) { str = p.getText(); - } else if (_skipNullValues && (t == JsonToken.VALUE_NULL)) { + } else if ((t == JsonToken.VALUE_NULL) && (_nuller != null)) { + _nuller.getNullValue(ctxt); continue; } else { CharSequence cs = (CharSequence) ctxt.handleUnexpectedToken(Character.TYPE, p); @@ -317,14 +363,14 @@ final static class BooleanDeser private static final long serialVersionUID = 1L; public BooleanDeser() { super(boolean[].class); } - protected BooleanDeser(BooleanDeser base, Nulls nullStyle, Boolean unwrapSingle) { - super(base, nullStyle, unwrapSingle); + protected BooleanDeser(BooleanDeser base, NullValueProvider nuller, Boolean unwrapSingle) { + super(base, nuller, unwrapSingle); } @Override - protected PrimitiveArrayDeserializers withResolved(Nulls nullStyle, + protected PrimitiveArrayDeserializers withResolved(NullValueProvider nuller, Boolean unwrapSingle) { - return new BooleanDeser(this, nullStyle, unwrapSingle); + return new BooleanDeser(this, nuller, unwrapSingle); } @Override @@ -351,8 +397,12 @@ public boolean[] deserialize(JsonParser p, DeserializationContext ctxt) value = true; } else if (t == JsonToken.VALUE_FALSE) { value = false; - } else if ((t == JsonToken.VALUE_NULL) && _skipNullValues) { - continue; + } else if (t == JsonToken.VALUE_NULL) { + if (_nuller != null) { + _nuller.getNullValue(ctxt); + continue; + } + value = false; } else { value = _parseBooleanPrimitive(p, ctxt); } @@ -395,14 +445,14 @@ final static class ByteDeser private static final long serialVersionUID = 1L; public ByteDeser() { super(byte[].class); } - protected ByteDeser(ByteDeser base, Nulls nullStyle,Boolean unwrapSingle) { - super(base, nullStyle, unwrapSingle); + protected ByteDeser(ByteDeser base, NullValueProvider nuller,Boolean unwrapSingle) { + super(base, nuller, unwrapSingle); } @Override - protected PrimitiveArrayDeserializers withResolved(Nulls nullStyle, + protected PrimitiveArrayDeserializers withResolved(NullValueProvider nuller, Boolean unwrapSingle) { - return new ByteDeser(this, nullStyle, unwrapSingle); + return new ByteDeser(this, nuller, unwrapSingle); } @Override @@ -456,7 +506,8 @@ public byte[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOEx } else { // should probably accept nulls as 0 if (t == JsonToken.VALUE_NULL) { - if (_skipNullValues) { + if (_nuller != null) { + _nuller.getNullValue(ctxt); continue; } value = (byte) 0; @@ -489,7 +540,8 @@ protected byte[] handleSingleElementUnwrapped(JsonParser p, } else { // should probably accept nulls as 'false' if (t == JsonToken.VALUE_NULL) { - if (_skipNullValues) { + if (_nuller != null) { + _nuller.getNullValue(ctxt); return (byte[]) getEmptyValue(ctxt); } return null; @@ -517,14 +569,14 @@ final static class ShortDeser private static final long serialVersionUID = 1L; public ShortDeser() { super(short[].class); } - protected ShortDeser(ShortDeser base, Nulls nullStyle, Boolean unwrapSingle) { - super(base, nullStyle, unwrapSingle); + protected ShortDeser(ShortDeser base, NullValueProvider nuller, Boolean unwrapSingle) { + super(base, nuller, unwrapSingle); } @Override - protected PrimitiveArrayDeserializers withResolved(Nulls nullStyle, + protected PrimitiveArrayDeserializers withResolved(NullValueProvider nuller, Boolean unwrapSingle) { - return new ShortDeser(this, nullStyle, unwrapSingle); + return new ShortDeser(this, nuller, unwrapSingle); } @Override @@ -545,11 +597,15 @@ public short[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOE try { JsonToken t; while ((t = p.nextToken()) != JsonToken.END_ARRAY) { - short value = _parseShortPrimitive(p, ctxt); + short value; if (t == JsonToken.VALUE_NULL) { - if (_skipNullValues) { + if (_nuller != null) { + _nuller.getNullValue(ctxt); continue; } + value = (short) 0; + } else { + value = _parseShortPrimitive(p, ctxt); } if (ix >= chunk.length) { chunk = builder.appendCompletedChunk(chunk, ix); @@ -588,14 +644,14 @@ final static class IntDeser public final static IntDeser instance = new IntDeser(); public IntDeser() { super(int[].class); } - protected IntDeser(IntDeser base, Nulls nullStyle, Boolean unwrapSingle) { - super(base, nullStyle, unwrapSingle); + protected IntDeser(IntDeser base, NullValueProvider nuller, Boolean unwrapSingle) { + super(base, nuller, unwrapSingle); } @Override - protected PrimitiveArrayDeserializers withResolved(Nulls nullStyle, + protected PrimitiveArrayDeserializers withResolved(NullValueProvider nuller, Boolean unwrapSingle) { - return new IntDeser(this, nullStyle, unwrapSingle); + return new IntDeser(this, nuller, unwrapSingle); } @Override @@ -620,7 +676,8 @@ public int[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOExc if (t == JsonToken.VALUE_NUMBER_INT) { value = p.getIntValue(); } else if (t == JsonToken.VALUE_NULL) { - if (_skipNullValues) { + if (_nuller != null) { + _nuller.getNullValue(ctxt); continue; } value = 0; @@ -664,14 +721,14 @@ final static class LongDeser public final static LongDeser instance = new LongDeser(); public LongDeser() { super(long[].class); } - protected LongDeser(LongDeser base, Nulls nullStyle, Boolean unwrapSingle) { - super(base, nullStyle, unwrapSingle); + protected LongDeser(LongDeser base, NullValueProvider nuller, Boolean unwrapSingle) { + super(base, nuller, unwrapSingle); } @Override - protected PrimitiveArrayDeserializers withResolved(Nulls nullStyle, + protected PrimitiveArrayDeserializers withResolved(NullValueProvider nuller, Boolean unwrapSingle) { - return new LongDeser(this, nullStyle, unwrapSingle); + return new LongDeser(this, nuller, unwrapSingle); } @Override @@ -696,7 +753,8 @@ public long[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOEx if (t == JsonToken.VALUE_NUMBER_INT) { value = p.getLongValue(); } else if (t == JsonToken.VALUE_NULL) { - if (_skipNullValues) { + if (_nuller != null) { + _nuller.getNullValue(ctxt); continue; } value = 0L; @@ -738,14 +796,14 @@ final static class FloatDeser private static final long serialVersionUID = 1L; public FloatDeser() { super(float[].class); } - protected FloatDeser(FloatDeser base, Nulls nullStyle, Boolean unwrapSingle) { - super(base, nullStyle, unwrapSingle); + protected FloatDeser(FloatDeser base, NullValueProvider nuller, Boolean unwrapSingle) { + super(base, nuller, unwrapSingle); } @Override - protected PrimitiveArrayDeserializers withResolved(Nulls nullStyle, + protected PrimitiveArrayDeserializers withResolved(NullValueProvider nuller, Boolean unwrapSingle) { - return new FloatDeser(this, nullStyle, unwrapSingle); + return new FloatDeser(this, nuller, unwrapSingle); } @Override @@ -768,7 +826,8 @@ public float[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOE while ((t = p.nextToken()) != JsonToken.END_ARRAY) { // whether we should allow truncating conversions? if (t == JsonToken.VALUE_NULL) { - if (_skipNullValues) { + if (_nuller != null) { + _nuller.getNullValue(ctxt); continue; } } @@ -808,14 +867,14 @@ final static class DoubleDeser private static final long serialVersionUID = 1L; public DoubleDeser() { super(double[].class); } - protected DoubleDeser(DoubleDeser base, Nulls nullStyle, Boolean unwrapSingle) { - super(base, nullStyle, unwrapSingle); + protected DoubleDeser(DoubleDeser base, NullValueProvider nuller, Boolean unwrapSingle) { + super(base, nuller, unwrapSingle); } @Override - protected PrimitiveArrayDeserializers withResolved(Nulls nullStyle, + protected PrimitiveArrayDeserializers withResolved(NullValueProvider nuller, Boolean unwrapSingle) { - return new DoubleDeser(this, nullStyle, unwrapSingle); + return new DoubleDeser(this, nuller, unwrapSingle); } @Override @@ -837,7 +896,8 @@ public double[] deserialize(JsonParser p, DeserializationContext ctxt) throws IO JsonToken t; while ((t = p.nextToken()) != JsonToken.END_ARRAY) { if (t == JsonToken.VALUE_NULL) { - if (_skipNullValues) { + if (_nuller != null) { + _nuller.getNullValue(ctxt); continue; } } diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/StdDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/StdDeserializer.java index 26b9e614b4..e5e34571ed 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/StdDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/StdDeserializer.java @@ -1140,7 +1140,7 @@ protected final NullValueProvider _findNullProvider(DeserializationContext ctxt, if (nulls != null) { switch (nulls) { case FAIL: - return new NullsFailProvider(prop.getFullName(), prop.getType()); + return NullsFailProvider.constructForProperty(prop); case AS_EMPTY: // can not deal with empty values if there is no value deserializer that // can indicate what "empty value" is: diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/filter/NullConversionsForContentTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/filter/NullConversionsForContentTest.java index 25a9dc94c4..d388ea58c8 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/filter/NullConversionsForContentTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/filter/NullConversionsForContentTest.java @@ -27,6 +27,11 @@ static class NullContentSkip { @JsonSetter(contentNulls=JsonSetter.Nulls.SKIP) public T values; } + + static class NullContentUndefined { + @JsonSetter // leave with defaults + public T values; + } private final ObjectMapper MAPPER = new ObjectMapper(); @@ -36,7 +41,20 @@ static class NullContentSkip { /********************************************************** */ - public void testFailOnNull() throws Exception + // Tests to verify that we can set default settings + public void testFailOnNullFromDefaults() throws Exception + { + final String JSON = aposToQuotes("{'values':[null]}"); + TypeReference listType = new TypeReference>>() { }; + + // by default fine to get nulls + NullContentUndefined> result = MAPPER.readValue(JSON, listType); + assertNotNull(result.values); + assertEquals(1, result.values.size()); + assertNull(result.values.get(0)); + } + + public void testFailOnNullWithCollections() throws Exception { TypeReference typeRef = new TypeReference>>() { }; @@ -87,10 +105,10 @@ public void testFailOnNullWithArrays() throws Exception } } - /* public void testFailOnNullWithPrimitiveArrays() throws Exception { final String JSON = aposToQuotes("{'noNulls':[null]}"); + // boolean[] try { MAPPER.readValue(JSON, new TypeReference>() { }); @@ -98,7 +116,6 @@ public void testFailOnNullWithPrimitiveArrays() throws Exception } catch (InvalidNullException e) { verifyException(e, "property \"noNulls\""); } - // int[] try { MAPPER.readValue(JSON, new TypeReference>() { }); @@ -106,8 +123,14 @@ public void testFailOnNullWithPrimitiveArrays() throws Exception } catch (InvalidNullException e) { verifyException(e, "property \"noNulls\""); } + // double[] + try { + MAPPER.readValue(JSON, new TypeReference>() { }); + fail("Should not pass"); + } catch (InvalidNullException e) { + verifyException(e, "property \"noNulls\""); + } } - */ public void testFailOnNullWithMaps() throws Exception {