Skip to content

Commit

Permalink
Fix #403
Browse files Browse the repository at this point in the history
  • Loading branch information
cowtowncoder committed Mar 8, 2017
1 parent b122636 commit 9446514
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 13 deletions.
5 changes: 5 additions & 0 deletions release-notes/CREDITS
Original file line number Diff line number Diff line change
Expand Up @@ -623,3 +623,8 @@ Lyor Goldstein (lgoldstein@github)
* Reported #1544: `EnumMapDeserializer` assumes a pure `EnumMap` and does not support
derived classes
(2.9.0)

Harleen Sahni (harleensahni@github)
* Reported #403: Make FAIL_ON_NULL_FOR_PRIMITIVES apply to primitive arrays and other
types that wrap primitives
(2.9.0)
2 changes: 2 additions & 0 deletions release-notes/VERSION
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ Project: jackson-databind
(reported by Starkom@github)
#357: StackOverflowError with contentConverter that returns array type
(reported by Florian S)
#403: Make FAIL_ON_NULL_FOR_PRIMITIVES apply to primitive arrays and other types that wrap primitives
(reported by Harleen S)
#476: Allow "Serialize as POJO" using `@JsonFormat(shape=Shape.OBJECT)` class annotation
#507: Support for default `@JsonView` for a class
(suggested by Mark W)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ protected Object _coerceTextualNull(DeserializationContext ctxt) throws JsonMapp
{
if (_primitive && ctxt.isEnabled(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)) {
ctxt.reportInputMismatch(this,
"Can not map String `null` into type %s (set DeserializationConfig.DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES to 'false' to allow)",
"Can not map String \"null\" into type %s (set DeserializationConfig.DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES to 'false' to allow)",
handledType().toString());
}
return _nullValue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -299,9 +299,13 @@ public char[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOEx
String str;
if (t == JsonToken.VALUE_STRING) {
str = p.getText();
} else if ((t == JsonToken.VALUE_NULL) && (_nuller != null)) {
_nuller.getNullValue(ctxt);
continue;
} else if (t == JsonToken.VALUE_NULL) {
if (_nuller != null) {
_nuller.getNullValue(ctxt);
continue;
}
_verifyPrimitiveNull(ctxt);
str = "\0";
} else {
CharSequence cs = (CharSequence) ctxt.handleUnexpectedToken(Character.TYPE, p);
str = cs.toString();
Expand Down Expand Up @@ -380,7 +384,7 @@ protected boolean[] _constructEmpty() {

@Override
public boolean[] deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException
throws IOException
{
if (!p.isExpectedStartArrayToken()) {
return handleNonArray(p, ctxt);
Expand All @@ -402,6 +406,7 @@ public boolean[] deserialize(JsonParser p, DeserializationContext ctxt)
_nuller.getNullValue(ctxt);
continue;
}
_verifyPrimitiveNull(ctxt);
value = false;
} else {
value = _parseBooleanPrimitive(p, ctxt);
Expand Down Expand Up @@ -510,10 +515,10 @@ public byte[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOEx
_nuller.getNullValue(ctxt);
continue;
}
_verifyPrimitiveNull(ctxt);
value = (byte) 0;
} else {
Number n = (Number) ctxt.handleUnexpectedToken(_valueClass.getComponentType(), p);
value = n.byteValue();
value = _parseBytePrimitive(p, ctxt);
}
}
if (ix >= chunk.length) {
Expand Down Expand Up @@ -544,6 +549,7 @@ protected byte[] handleSingleElementUnwrapped(JsonParser p,
_nuller.getNullValue(ctxt);
return (byte[]) getEmptyValue(ctxt);
}
_verifyPrimitiveNull(ctxt);
return null;
}
Number n = (Number) ctxt.handleUnexpectedToken(_valueClass.getComponentType(), p);
Expand Down Expand Up @@ -603,6 +609,7 @@ public short[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOE
_nuller.getNullValue(ctxt);
continue;
}
_verifyPrimitiveNull(ctxt);
value = (short) 0;
} else {
value = _parseShortPrimitive(p, ctxt);
Expand Down Expand Up @@ -680,6 +687,7 @@ public int[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOExc
_nuller.getNullValue(ctxt);
continue;
}
_verifyPrimitiveNull(ctxt);
value = 0;
} else {
value = _parseIntPrimitive(p, ctxt);
Expand Down Expand Up @@ -757,6 +765,7 @@ public long[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOEx
_nuller.getNullValue(ctxt);
continue;
}
_verifyPrimitiveNull(ctxt);
value = 0L;
} else {
value = _parseLongPrimitive(p, ctxt);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,10 @@ protected final boolean _parseBooleanPrimitive(JsonParser p, DeserializationCont
JsonToken t = p.getCurrentToken();
if (t == JsonToken.VALUE_TRUE) return true;
if (t == JsonToken.VALUE_FALSE) return false;
if (t == JsonToken.VALUE_NULL) return false;
if (t == JsonToken.VALUE_NULL) {
_verifyPrimitiveNull(ctxt);
return false;
}

// should accept ints too, (0 == false, otherwise true)
if (t == JsonToken.VALUE_NUMBER_INT) {
Expand All @@ -155,10 +158,11 @@ protected final boolean _parseBooleanPrimitive(JsonParser p, DeserializationCont
if ("true".equals(text) || "True".equals(text)) {
return true;
}
if ("false".equals(text) || "False".equals(text) || text.length() == 0) {
if ("false".equals(text) || "False".equals(text)) {
return false;
}
if (_hasTextualNull(text)) {
if ((text.length() == 0) || _hasTextualNull(text)) {
_verifyPrimitiveNullCoercion(ctxt, text);
return false;
}
Boolean b = (Boolean) ctxt.handleWeirdStringValue(_valueClass, text,
Expand Down Expand Up @@ -192,6 +196,19 @@ protected boolean _parseBooleanFromInt(JsonParser p, DeserializationContext ctxt
return !"0".equals(p.getText());
}

protected final byte _parseBytePrimitive(JsonParser p, DeserializationContext ctxt)
throws IOException
{
int value = _parseIntPrimitive(p, ctxt);
// So far so good: but does it fit?
if (_byteOverflow(value)) {
Number v = (Number) ctxt.handleWeirdStringValue(_valueClass, String.valueOf(value),
"overflow, value can not be represented as 8-bit value");
return (v == null) ? (byte) 0 : v.byteValue();
}
return (byte) value;
}

protected final short _parseShortPrimitive(JsonParser p, DeserializationContext ctxt)
throws IOException
{
Expand All @@ -214,7 +231,8 @@ protected final int _parseIntPrimitive(JsonParser p, DeserializationContext ctxt
JsonToken t = p.getCurrentToken();
if (t == JsonToken.VALUE_STRING) { // let's do implicit re-parse
String text = p.getText().trim();
if (_hasTextualNull(text)) {
if ((text.length() == 0) || _hasTextualNull(text)) {
_verifyPrimitiveNullCoercion(ctxt, text);
return 0;
}
try {
Expand Down Expand Up @@ -246,6 +264,7 @@ protected final int _parseIntPrimitive(JsonParser p, DeserializationContext ctxt
return p.getValueAsInt();
}
if (t == JsonToken.VALUE_NULL) {
_verifyPrimitiveNull(ctxt);
return 0;
}
if (t == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
Expand Down Expand Up @@ -275,6 +294,7 @@ protected final long _parseLongPrimitive(JsonParser p, DeserializationContext ct
case JsonTokenId.ID_STRING:
String text = p.getText().trim();
if (text.length() == 0 || _hasTextualNull(text)) {
_verifyPrimitiveNullCoercion(ctxt, text);
return 0L;
}
try {
Expand All @@ -286,6 +306,7 @@ protected final long _parseLongPrimitive(JsonParser p, DeserializationContext ct
return (v == null) ? 0 : v.longValue();
}
case JsonTokenId.ID_NULL:
_verifyPrimitiveNull(ctxt);
return 0L;
case JsonTokenId.ID_START_ARRAY:
if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
Expand Down Expand Up @@ -313,6 +334,7 @@ protected final float _parseFloatPrimitive(JsonParser p, DeserializationContext
if (t == JsonToken.VALUE_STRING) {
String text = p.getText().trim();
if (text.length() == 0 || _hasTextualNull(text)) {
_verifyPrimitiveNullCoercion(ctxt, text);
return 0.0f;
}
switch (text.charAt(0)) {
Expand All @@ -338,6 +360,7 @@ protected final float _parseFloatPrimitive(JsonParser p, DeserializationContext
return (v == null) ? 0 : v.floatValue();
}
if (t == JsonToken.VALUE_NULL) {
_verifyPrimitiveNull(ctxt);
return 0.0f;
}
if (t == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
Expand Down Expand Up @@ -366,6 +389,7 @@ protected final double _parseDoublePrimitive(JsonParser p, DeserializationContex
if (t == JsonToken.VALUE_STRING) {
String text = p.getText().trim();
if (text.length() == 0 || _hasTextualNull(text)) {
_verifyPrimitiveNullCoercion(ctxt, text);
return 0.0;
}
switch (text.charAt(0)) {
Expand Down Expand Up @@ -393,6 +417,7 @@ protected final double _parseDoublePrimitive(JsonParser p, DeserializationContex
return (v == null) ? 0 : v.doubleValue();
}
if (t == JsonToken.VALUE_NULL) {
_verifyPrimitiveNull(ctxt);
return 0.0;
}
// [databind#381]
Expand Down Expand Up @@ -859,6 +884,25 @@ protected void _failDoubleToIntCoercion(JsonParser p, DeserializationContext ctx
p.getValueAsString(), type);
}

protected final void _verifyPrimitiveNull(DeserializationContext ctxt) throws IOException
{
if (ctxt.isEnabled(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)) {
ctxt.reportInputMismatch(this,
"Can not map `null` into primitive contents of type %s (set DeserializationConfig.DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES to 'false' to allow)",
handledType().getSimpleName());
}
}

protected final void _verifyPrimitiveNullCoercion(DeserializationContext ctxt, String str) throws IOException
{
if (ctxt.isEnabled(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)) {
ctxt.reportInputMismatch(this,
"Can not map String \"%s\" into primitive contents of type %s (set DeserializationConfig.DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES to 'false' to allow)",
str,
handledType().getSimpleName());
}
}

/*
/**********************************************************
/* Helper methods, other
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.fasterxml.jackson.databind.deser.jdk;

import java.io.*;
import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.math.BigInteger;

Expand Down Expand Up @@ -1062,12 +1063,73 @@ public void testEmptyStringForPrimitives() throws IOException
assertEquals(0.0, bean.doubleValue);
}

// for [databind#403]
public void testEmptyStringFailForPrimitives() throws IOException
{
final ObjectReader reader = MAPPER
.readerFor(PrimitivesBean.class)
.with(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES);

// boolean
try {
reader.readValue("{\"booleanValue\":\"\"}");
fail("Expected failure for boolean + empty String");
} catch (JsonMappingException e) {
verifyException(e, "Can not map empty String (\"\") into type boolean");
}
// byte/char/short/int/long
try {
reader.readValue("{\"byteValue\":\"\"}");
fail("Expected failure for byte + empty String");
} catch (JsonMappingException e) {
verifyException(e, "Can not map empty String (\"\") into type byte");
}
try {
reader.readValue("{\"charValue\":\"\"}");
fail("Expected failure for char + empty String");
} catch (JsonMappingException e) {
verifyException(e, "Can not map empty String (\"\") into type char");
}
try {
reader.readValue("{\"shortValue\":\"\"}");
fail("Expected failure for short + empty String");
} catch (JsonMappingException e) {
verifyException(e, "Can not map empty String (\"\") into type short");
}
try {
reader.readValue("{\"intValue\":\"\"}");
fail("Expected failure for int + empty String");
} catch (JsonMappingException e) {
verifyException(e, "Can not map empty String (\"\") into type int");
}
try {
reader.readValue("{\"longValue\":\"\"}");
fail("Expected failure for long + empty String");
} catch (JsonMappingException e) {
verifyException(e, "Can not map empty String (\"\") into type long");
}

// float/double
try {
reader.readValue("{\"floatValue\":\"\"}");
fail("Expected failure for float + empty String");
} catch (JsonMappingException e) {
verifyException(e, "Can not map empty String (\"\") into type float");
}
try {
reader.readValue("{\"doubleValue\":\"\"}");
fail("Expected failure for double + empty String");
} catch (JsonMappingException e) {
verifyException(e, "Can not map empty String (\"\") into type double");
}
}

/*
/**********************************************************
/* Null handling for scalars in POJO
/**********************************************************
*/

public void testNullForPrimitives() throws IOException
{
// by default, ok to rely on defaults
Expand All @@ -1085,7 +1147,7 @@ public void testNullForPrimitives() throws IOException
assertEquals((byte) 0, bean.byteValue);
assertEquals(0L, bean.longValue);
assertEquals(0.0f, bean.floatValue);

// but not when enabled
final ObjectReader reader = MAPPER
.readerFor(PrimitivesBean.class)
Expand Down Expand Up @@ -1143,4 +1205,54 @@ public void testNullForPrimitives() throws IOException
verifyException(e, "Can not map `null` into type double");
}
}

public void testNullForPrimitiveArrays() throws IOException
{
_testNullForPrimitiveArrays(boolean[].class, Boolean.FALSE);
_testNullForPrimitiveArrays(byte[].class, Byte.valueOf((byte) 0));
_testNullForPrimitiveArrays(char[].class, Character.valueOf((char) 0), false);
_testNullForPrimitiveArrays(short[].class, Short.valueOf((short)0));
_testNullForPrimitiveArrays(int[].class, Integer.valueOf(0));
_testNullForPrimitiveArrays(long[].class, Long.valueOf(0L));
_testNullForPrimitiveArrays(float[].class, Float.valueOf(0f));
_testNullForPrimitiveArrays(double[].class, Double.valueOf(0d));
}

private void _testNullForPrimitiveArrays(Class<?> cls, Object defValue) throws IOException {
_testNullForPrimitiveArrays(cls, defValue, true);
}

private void _testNullForPrimitiveArrays(Class<?> cls, Object defValue,
boolean testEmptyString) throws IOException
{
final String EMPTY_STRING_JSON = "[ \"\" ]";
final String JSON_WITH_NULL = "[ null ]";
final String SIMPLE_NAME = cls.getSimpleName();
final ObjectReader readerCoerceOk = MAPPER.readerFor(cls);
final ObjectReader readerNoCoerce = readerCoerceOk
.with(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES);

Object ob = readerCoerceOk.forType(cls).readValue(JSON_WITH_NULL);
assertEquals(1, Array.getLength(ob));
assertEquals(defValue, Array.get(ob, 0));
try {
readerNoCoerce.readValue(JSON_WITH_NULL);
fail("Should not pass");
} catch (JsonMappingException e) {
verifyException(e, "Can not map `null` into primitive contents of type "+SIMPLE_NAME);
}

if (testEmptyString) {
ob = readerCoerceOk.forType(cls).readValue(EMPTY_STRING_JSON);
assertEquals(1, Array.getLength(ob));
assertEquals(defValue, Array.get(ob, 0));

try {
readerNoCoerce.readValue(EMPTY_STRING_JSON);
fail("Should not pass");
} catch (JsonMappingException e) {
verifyException(e, "Can not map String \"\" into primitive contents of type "+SIMPLE_NAME);
}
}
}
}

0 comments on commit 9446514

Please sign in to comment.