diff --git a/release-notes/VERSION b/release-notes/VERSION
index a8810f2faf..fd4597f0c5 100644
--- a/release-notes/VERSION
+++ b/release-notes/VERSION
@@ -49,6 +49,10 @@ Versions: 3.x (for earlier see VERSION-2.x)
#5456: Additional configuration (`JsonNodeFeature.STRIP_TRAILING_BIGDECIMAL_ZEROES`: true)
to MapperBuilder#configureForJackson2 to closer match Jackson 2 behavior
(contributed by @nrayburn-tech)
+#5469 : Add callback in `DeserializationProblemHandler` for "null for primitive"
+ problem case
+ (reported by @jnizet)
+ (fixed by Joo-Hyuk K)
#5475: Support `@JsonDeserializeAs` annotation
(implemented by @cowtowncoder, w/ Claude code)
diff --git a/src/main/java/tools/jackson/databind/DeserializationContext.java b/src/main/java/tools/jackson/databind/DeserializationContext.java
index 65f7d8b176..c30afb11d5 100644
--- a/src/main/java/tools/jackson/databind/DeserializationContext.java
+++ b/src/main/java/tools/jackson/databind/DeserializationContext.java
@@ -1344,7 +1344,7 @@ public Object handleWeirdStringValue(Class> targetClass, String value,
return instance;
}
throw weirdStringException(value, targetClass, String.format(
- "DeserializationProblemHandler.handleWeirdStringValue() for type %s returned value of type %s",
+"`DeserializationProblemHandler.handleWeirdStringValue()` for type %s returned value of type %s",
ClassUtil.getClassDescription(targetClass),
ClassUtil.getClassDescription(instance)
));
@@ -1387,7 +1387,7 @@ public Object handleWeirdNumberValue(Class> targetClass, Number value,
return key;
}
throw weirdNumberException(value, targetClass, _format(
- "DeserializationProblemHandler.handleWeirdNumberValue() for type %s returned value of type %s",
+"`DeserializationProblemHandler.handleWeirdNumberValue()` for type %s returned value of type %s",
ClassUtil.getClassDescription(targetClass),
ClassUtil.getClassDescription(key)
));
@@ -1412,7 +1412,7 @@ public Object handleWeirdNativeValue(JavaType targetType, Object badValue,
return goodValue;
}
throw DatabindException.from(p, _format(
-"DeserializationProblemHandler.handleWeirdNativeValue() for type %s returned value of type %s",
+"`DeserializationProblemHandler.handleWeirdNativeValue()` for type %s returned value of type %s",
ClassUtil.getClassDescription(targetType),
ClassUtil.getClassDescription(goodValue)
));
@@ -1421,6 +1421,51 @@ public Object handleWeirdNativeValue(JavaType targetType, Object badValue,
throw weirdNativeValueException(badValue, raw);
}
+ /**
+ * Method that deserializers should call if they encounter a null value and
+ * target value type is a Primitive type.
+ * Default implementation will try to call {@link DeserializationProblemHandler#handleNullForPrimitives}
+ * on configured handlers, if any, to allow for recovery; if recovery does not
+ * succeed, will call {@link #reportInputMismatch} with given message,
+ * which will throw {@link MismatchedInputException}.
+ *
+ * @param targetClass Primitive type into which incoming {@code null} value should be converted to
+ * @param p Parser that points to the {@code null} read
+ * @param deser Type of {@link ValueDeserializer} calling this method
+ * @param msgTemplate Error message template caller wants to use if exception is to be thrown
+ * @param msgArgs Arguments for {@code msgTemplate} (if any)
+ *
+ * @throws JacksonException To indicate unrecoverable problem, usually based on msg
+ *
+ * @since 3.1
+ */
+ public Object handleNullForPrimitives(Class> targetClass,
+ JsonParser p, ValueDeserializer> deser,
+ String msgTemplate, Object... msgArgs)
+ throws JacksonException
+ {
+ // but if not handled, just throw exception
+ LinkedNode h = _config.getProblemHandlers();
+ String msg = _format(msgTemplate, msgArgs);
+ while (h != null) {
+ // Can bail out if it's handled
+ Object instance = h.value().handleNullForPrimitives(this, targetClass, p, deser, msg);
+ if (instance != DeserializationProblemHandler.NOT_HANDLED) {
+ // Sanity check for broken handlers, otherwise nasty to debug:
+ if (_isCompatible(targetClass, instance)) {
+ return instance;
+ }
+ // In case our problem handler providing incompatible value,
+ throw new InvalidFormatException(_parser,
+String.format("`DeserializationProblemHandler.handleNullForPrimitives()` for type %s returned value of type %s",
+ ClassUtil.nameOf(targetClass), ClassUtil.getClassDescription(instance)),
+ instance, targetClass
+ );
+ }
+ h = h.next();
+ }
+ return reportInputMismatch(deser, msg);
+ }
/**
* Method that deserializers should call if they fail to instantiate value
* due to lack of viable instantiator (usually creator, that is, constructor
@@ -1432,7 +1477,7 @@ public Object handleWeirdNativeValue(JavaType targetType, Object badValue,
* @param instClass Type that was to be instantiated
* @param valueInst (optional) Value instantiator to be used, if any; null if type does not
* use one for instantiation (custom deserialiers don't; standard POJO deserializer does)
- * @param p Parser that points to the JSON value to decode
+ * @param p Parser that points to the input value to decode
*
* @return Object that should be constructed, if any; has to be of type instClass
*/
@@ -1456,7 +1501,7 @@ public Object handleMissingInstantiator(Class> instClass, ValueInstantiator va
return instance;
}
reportBadDefinition(constructType(instClass), String.format(
-"DeserializationProblemHandler.handleMissingInstantiator() for type %s returned value of type %s",
+"`DeserializationProblemHandler.handleMissingInstantiator()` for type %s returned value of type %s",
ClassUtil.getClassDescription(instClass),
ClassUtil.getClassDescription((instance)
)));
diff --git a/src/main/java/tools/jackson/databind/deser/DeserializationProblemHandler.java b/src/main/java/tools/jackson/databind/deser/DeserializationProblemHandler.java
index 8cd736b745..ee33a7181b 100644
--- a/src/main/java/tools/jackson/databind/deser/DeserializationProblemHandler.java
+++ b/src/main/java/tools/jackson/databind/deser/DeserializationProblemHandler.java
@@ -221,6 +221,42 @@ public Object handleUnexpectedToken(DeserializationContext ctxt,
return NOT_HANDLED;
}
+ /**
+ * Method that deserializers should call if the {@code null} value is encountered when
+ * deserializing a Java primitive types ({@code int}, {@code long} etc) and
+ * {@link DeserializationFeature#FAIL_ON_NULL_FOR_PRIMITIVES} is enabled.
+ * Handler needs to do one of:
+ *
+ * - Indicate it does not know what to do by returning {@link #NOT_HANDLED}
+ *
+ * - Throw a {@link JacksonException} to indicate specific fail message (instead of
+ * standard exception caller would throw)
+ *
+ * - Handle content to match (by consuming or skipping it), and return actual
+ * instantiated value (of type {@code targetType}) to use as replacement;
+ * (of expected target type). NOTE: handler SHOULD NOT return {@code null}.
+ *
+ *
+ *
+ * @param ctxt Deserialization context
+ * @param targetType Target type to deserialize into
+ * @param p JsonParser used to read {@code null} input token
+ * @param deser Target deserializer that attempted to deserialize {@code null} target value
+ * @param failureMsg Message that will be used by caller to indicate type of failure unless
+ * handler produces a value to use
+ *
+ *
+ * @return Either {@link #NOT_HANDLED} to indicate that handler does not know
+ * what to do (and exception may be thrown), or value to use (possibly
+ * null
+ */
+ public Object handleNullForPrimitives(DeserializationContext ctxt,
+ Class> targetType, JsonParser p, ValueDeserializer> deser, String failureMsg)
+ throws JacksonException
+ {
+ return NOT_HANDLED;
+ }
+
/**
* Method called when instance creation for a type fails due to an exception.
* Handler may choose to do one of following things:
diff --git a/src/main/java/tools/jackson/databind/deser/jdk/NumberDeserializers.java b/src/main/java/tools/jackson/databind/deser/jdk/NumberDeserializers.java
index ce2303f14c..e0bc206d07 100644
--- a/src/main/java/tools/jackson/databind/deser/jdk/NumberDeserializers.java
+++ b/src/main/java/tools/jackson/databind/deser/jdk/NumberDeserializers.java
@@ -159,14 +159,15 @@ public AccessPattern getNullAccessPattern() {
return AccessPattern.CONSTANT;
}
+ @SuppressWarnings("unchecked")
@Override
public final T getNullValue(DeserializationContext ctxt) {
// 01-Mar-2017, tatu: Alas, not all paths lead to `_coerceNull()`, as `SettableBeanProperty`
// short-circuits `null` handling. Hence need this check as well.
if (_primitive && ctxt.isEnabled(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)) {
- ctxt.reportInputMismatch(this,
- "Cannot map `null` into type %s (set DeserializationConfig.DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES to 'false' to allow)",
- ClassUtil.classNameOf(handledType()));
+ return (T) ctxt.handleNullForPrimitives(handledType(), ctxt.getParser(), this,
+"Cannot map `null` into type %s (set `DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES` to 'false' to allow)",
+ ClassUtil.nameOf(handledType()));
}
return _nullValue;
}
@@ -476,7 +477,8 @@ public Character deserialize(JsonParser p, DeserializationContext ctxt)
"value outside valid Character range (0x0000 - 0xFFFF)");
case JsonTokenId.ID_NULL:
if (_primitive) {
- _verifyNullForPrimitive(ctxt);
+ char c = (char) _verifyNullForPrimitive(ctxt, p, '\0');
+ return c;
}
return (Character) getNullValue(ctxt);
case JsonTokenId.ID_START_ARRAY:
diff --git a/src/main/java/tools/jackson/databind/deser/jdk/PrimitiveArrayDeserializers.java b/src/main/java/tools/jackson/databind/deser/jdk/PrimitiveArrayDeserializers.java
index 7603bb662f..4aeeed10f0 100644
--- a/src/main/java/tools/jackson/databind/deser/jdk/PrimitiveArrayDeserializers.java
+++ b/src/main/java/tools/jackson/databind/deser/jdk/PrimitiveArrayDeserializers.java
@@ -282,8 +282,9 @@ public char[] deserialize(JsonParser p, DeserializationContext ctxt) throws Jack
_nuller.getNullValue(ctxt);
continue;
}
- _verifyNullForPrimitive(ctxt);
- str = "\0";
+ char value = (char) _verifyNullForPrimitive(ctxt, p, '\0');
+ sb.append(value);
+ continue;
} else {
CharSequence cs = (CharSequence) ctxt.handleUnexpectedToken(getValueType(ctxt), p);
str = cs.toString();
@@ -382,8 +383,7 @@ public boolean[] deserialize(JsonParser p, DeserializationContext ctxt)
_nuller.getNullValue(ctxt);
continue;
}
- _verifyNullForPrimitive(ctxt);
- value = false;
+ value = (boolean) _verifyNullForPrimitive(ctxt, p, false);
} else {
value = _parseBooleanPrimitive(p, ctxt);
}
@@ -497,8 +497,7 @@ public byte[] deserialize(JsonParser p, DeserializationContext ctxt) throws Jack
_nuller.getNullValue(ctxt);
continue;
}
- _verifyNullForPrimitive(ctxt);
- value = (byte) 0;
+ value = (byte) _verifyNullForPrimitive(ctxt, p, (byte) 0);
} else {
value = _parseBytePrimitive(p, ctxt);
}
@@ -525,14 +524,13 @@ protected byte[] handleSingleElementUnwrapped(JsonParser p,
if (t == JsonToken.VALUE_NUMBER_INT) {
value = p.getByteValue(); // note: may throw due to overflow
} else {
- // should probably accept nulls as 'false'
+ // should we accept nulls as null for byte[], or { 0 } ?
if (t == JsonToken.VALUE_NULL) {
if (_nuller != null) {
_nuller.getNullValue(ctxt);
return (byte[]) getEmptyValue(ctxt);
}
- _verifyNullForPrimitive(ctxt);
- return null;
+ value = (byte) _verifyNullForPrimitive(ctxt, p, (byte) 0);
}
Number n = (Number) ctxt.handleUnexpectedToken(getValueType(ctxt), p);
value = n.byteValue();
@@ -589,8 +587,7 @@ public short[] deserialize(JsonParser p, DeserializationContext ctxt) throws Jac
_nuller.getNullValue(ctxt);
continue;
}
- _verifyNullForPrimitive(ctxt);
- value = (short) 0;
+ value = (short) _verifyNullForPrimitive(ctxt, p, (short) 0);
} else {
value = _parseShortPrimitive(p, ctxt);
}
@@ -666,8 +663,7 @@ public int[] deserialize(JsonParser p, DeserializationContext ctxt) throws Jacks
_nuller.getNullValue(ctxt);
continue;
}
- _verifyNullForPrimitive(ctxt);
- value = 0;
+ value = (int) _verifyNullForPrimitive(ctxt, p, 0);
} else {
value = _parseIntPrimitive(p, ctxt);
}
@@ -743,8 +739,7 @@ public long[] deserialize(JsonParser p, DeserializationContext ctxt) throws Jack
_nuller.getNullValue(ctxt);
continue;
}
- _verifyNullForPrimitive(ctxt);
- value = 0L;
+ value = (long) _verifyNullForPrimitive(ctxt, p, 0L);
} else {
value = _parseLongPrimitive(p, ctxt);
}
diff --git a/src/main/java/tools/jackson/databind/deser/std/StdDeserializer.java b/src/main/java/tools/jackson/databind/deser/std/StdDeserializer.java
index 4ac56aecea..12cd3809ff 100644
--- a/src/main/java/tools/jackson/databind/deser/std/StdDeserializer.java
+++ b/src/main/java/tools/jackson/databind/deser/std/StdDeserializer.java
@@ -348,10 +348,10 @@ protected final boolean _parseBooleanPrimitive(JsonParser p, DeserializationCont
return true;
case JsonTokenId.ID_FALSE:
return false;
- case JsonTokenId.ID_NULL: // null fine for non-primitive
- _verifyNullForPrimitive(ctxt);
- return false;
- // 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
+ case JsonTokenId.ID_NULL: // null may or may be ok for primitive
+ return (boolean) _verifyNullForPrimitive(ctxt, p, false);
+
+ // 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
case JsonTokenId.ID_START_OBJECT:
text = ctxt.extractScalarFromObject(p, this, Boolean.TYPE);
// 17-May-2025, tatu: [databind#4656] need to check for `null`
@@ -377,8 +377,7 @@ protected final boolean _parseBooleanPrimitive(JsonParser p, DeserializationCont
final CoercionAction act = _checkFromStringCoercion(ctxt, text,
LogicalType.Boolean, Boolean.TYPE);
if (act == CoercionAction.AsNull) {
- _verifyNullForPrimitive(ctxt);
- return false;
+ return (boolean) _verifyNullForPrimitive(ctxt, p, false);
}
if (act == CoercionAction.AsEmpty) {
return false;
@@ -535,8 +534,7 @@ protected final byte _parseBytePrimitive(JsonParser p, DeserializationContext ct
case JsonTokenId.ID_NUMBER_INT:
return p.getByteValue();
case JsonTokenId.ID_NULL:
- _verifyNullForPrimitive(ctxt);
- return (byte) 0;
+ return (byte) _verifyNullForPrimitive(ctxt, p, (byte) 0);
// 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
case JsonTokenId.ID_START_OBJECT:
text = ctxt.extractScalarFromObject(p, this, Byte.TYPE);
@@ -565,8 +563,7 @@ protected final byte _parseBytePrimitive(JsonParser p, DeserializationContext ct
LogicalType.Integer, Byte.TYPE);
if (act == CoercionAction.AsNull) {
// 03-May-2021, tatu: Might not be allowed (should we do "empty" check?)
- _verifyNullForPrimitive(ctxt);
- return (byte) 0;
+ return (byte) _verifyNullForPrimitive(ctxt, p, (byte) 0);
}
if (act == CoercionAction.AsEmpty) {
return (byte) 0;
@@ -619,8 +616,7 @@ protected final short _parseShortPrimitive(JsonParser p, DeserializationContext
case JsonTokenId.ID_NUMBER_INT:
return p.getShortValue();
case JsonTokenId.ID_NULL:
- _verifyNullForPrimitive(ctxt);
- return (short) 0;
+ return (short) _verifyNullForPrimitive(ctxt, p, (short) 0);
// 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
case JsonTokenId.ID_START_OBJECT:
text = ctxt.extractScalarFromObject(p, this, Short.TYPE);
@@ -648,8 +644,7 @@ protected final short _parseShortPrimitive(JsonParser p, DeserializationContext
LogicalType.Integer, Short.TYPE);
if (act == CoercionAction.AsNull) {
// 03-May-2021, tatu: Might not be allowed (should we do "empty" check?)
- _verifyNullForPrimitive(ctxt);
- return (short) 0;
+ return (short) _verifyNullForPrimitive(ctxt, p, (short) 0);
}
if (act == CoercionAction.AsEmpty) {
return (short) 0;
@@ -696,8 +691,7 @@ protected int _parseIntPrimitive(JsonParser p, DeserializationContext ctxt)
// Here regular (strict) accessor is fine
return p.getIntValue();
case JsonTokenId.ID_NULL:
- _verifyNullForPrimitive(ctxt);
- return 0;
+ return (int) _verifyNullForPrimitive(ctxt, p, 0);
// 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
case JsonTokenId.ID_START_OBJECT:
text = ctxt.extractScalarFromObject(p, this, Integer.TYPE);
@@ -724,8 +718,7 @@ protected int _parseIntPrimitive(JsonParser p, DeserializationContext ctxt)
LogicalType.Integer, Integer.TYPE);
if (act == CoercionAction.AsNull) {
// 03-May-2021, tatu: Might not be allowed (should we do "empty" check?)
- _verifyNullForPrimitive(ctxt);
- return 0;
+ return (int) _verifyNullForPrimitive(ctxt, p, 0);
}
if (act == CoercionAction.AsEmpty) {
return 0;
@@ -852,8 +845,7 @@ protected final long _parseLongPrimitive(JsonParser p, DeserializationContext ct
case JsonTokenId.ID_NUMBER_INT:
return p.getLongValue();
case JsonTokenId.ID_NULL:
- _verifyNullForPrimitive(ctxt);
- return 0L;
+ return (long) _verifyNullForPrimitive(ctxt, p, 0L);
// 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
case JsonTokenId.ID_START_OBJECT:
text = ctxt.extractScalarFromObject(p, this, Long.TYPE);
@@ -880,8 +872,7 @@ protected final long _parseLongPrimitive(JsonParser p, DeserializationContext ct
LogicalType.Integer, Long.TYPE);
if (act == CoercionAction.AsNull) {
// 03-May-2021, tatu: Might not be allowed (should we do "empty" check?)
- _verifyNullForPrimitive(ctxt);
- return 0L;
+ return (long) _verifyNullForPrimitive(ctxt, p, 0L);
}
if (act == CoercionAction.AsEmpty) {
return 0L;
@@ -992,8 +983,7 @@ protected final float _parseFloatPrimitive(JsonParser p, DeserializationContext
case JsonTokenId.ID_NUMBER_FLOAT:
return p.getFloatValue();
case JsonTokenId.ID_NULL:
- _verifyNullForPrimitive(ctxt);
- return 0f;
+ return (float) _verifyNullForPrimitive(ctxt, p, 0f);
// 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
case JsonTokenId.ID_START_OBJECT:
text = ctxt.extractScalarFromObject(p, this, Float.TYPE);
@@ -1030,8 +1020,7 @@ protected final float _parseFloatPrimitive(JsonParser p, DeserializationContext
LogicalType.Integer, Float.TYPE);
if (act == CoercionAction.AsNull) {
// 03-May-2021, tatu: Might not be allowed (should we do "empty" check?)
- _verifyNullForPrimitive(ctxt);
- return 0.0f;
+ return (float) _verifyNullForPrimitive(ctxt, p, 0.0f);
}
if (act == CoercionAction.AsEmpty) {
return 0.0f;
@@ -1112,8 +1101,7 @@ protected final double _parseDoublePrimitive(JsonParser p, DeserializationContex
case JsonTokenId.ID_NUMBER_FLOAT:
return p.getDoubleValue();
case JsonTokenId.ID_NULL:
- _verifyNullForPrimitive(ctxt);
- return 0.0;
+ return (double)_verifyNullForPrimitive(ctxt, p, 0.0);
// 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
case JsonTokenId.ID_START_OBJECT:
text = ctxt.extractScalarFromObject(p, this, Double.TYPE);
@@ -1150,8 +1138,7 @@ protected final double _parseDoublePrimitive(JsonParser p, DeserializationContex
LogicalType.Integer, Double.TYPE);
if (act == CoercionAction.AsNull) {
// 03-May-2021, tatu: Might not be allowed (should we do "empty" check?)
- _verifyNullForPrimitive(ctxt);
- return 0.0;
+ return (double)_verifyNullForPrimitive(ctxt, p, 0.0);
}
if (act == CoercionAction.AsEmpty) {
return 0.0;
@@ -1634,16 +1621,29 @@ protected Object _coerceIntegral(JsonParser p, DeserializationContext ctxt) thro
* was received by other means (coerced due to configuration, or even from
* optionally acceptable String {@code "null"} token).
*/
- protected final void _verifyNullForPrimitive(DeserializationContext ctxt)
+ protected Object _verifyNullForPrimitive(DeserializationContext ctxt,
+ JsonParser p, Object defaultValue)
throws DatabindException
{
if (ctxt.isEnabled(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)) {
- ctxt.reportInputMismatch(this,
+ return ctxt.handleNullForPrimitives(handledType(), p,
+ this,
"Cannot coerce `null` to %s (disable `DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES` to allow)",
- _coercedTypeDesc());
+ _coercedTypeDesc());
}
+ return defaultValue;
}
+ /**
+ * @deprecated Since 3.1 use overload {@link #_verifyNullForPrimitive(DeserializationContext, JsonParser, Object)}
+ */
+ @Deprecated // @since 3.1
+ protected final void _verifyNullForPrimitive(DeserializationContext ctxt)
+ throws DatabindException
+ {
+ _verifyNullForPrimitive(ctxt, ctxt.getParser(), null);
+ }
+
/**
* Method called to verify that text value {@code "null"} from input is acceptable
* for primitive (unboxed) target type. It should not be called if actual
diff --git a/src/test/java/tools/jackson/databind/deser/filter/DeserializationProblemHandler5469Test.java b/src/test/java/tools/jackson/databind/deser/filter/DeserializationProblemHandler5469Test.java
new file mode 100644
index 0000000000..f69b53bf7e
--- /dev/null
+++ b/src/test/java/tools/jackson/databind/deser/filter/DeserializationProblemHandler5469Test.java
@@ -0,0 +1,103 @@
+package tools.jackson.databind.deser.filter;
+
+import org.junit.jupiter.api.Test;
+import tools.jackson.core.JacksonException;
+import tools.jackson.core.JsonParser;
+import tools.jackson.databind.*;
+import tools.jackson.databind.deser.DeserializationProblemHandler;
+import tools.jackson.databind.exc.InvalidFormatException;
+import tools.jackson.databind.json.JsonMapper;
+import tools.jackson.databind.testutil.DatabindTestUtil;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+// For [databind#5469] Add callback to signal null for primitive in DeserializationProblemHandler
+public class DeserializationProblemHandler5469Test
+ extends DatabindTestUtil
+{
+
+ static class Person5469 {
+ public String id;
+ public String name;
+ public long age;
+ }
+
+ private static int hitCountFirst = 0;
+ static class ProblemHandler5469 extends DeserializationProblemHandler
+ {
+ @Override
+ public Object handleNullForPrimitives(DeserializationContext ctxt, Class> targetType,
+ JsonParser p, ValueDeserializer> deser, String failureMsg
+ ) throws JacksonException {
+ hitCountFirst++;
+ return 5469L;
+ }
+ }
+
+ private static int hitCountSecond = 0;
+ static class MoreProblemHandler5469 extends DeserializationProblemHandler
+ {
+ @Override
+ public Object handleNullForPrimitives(DeserializationContext ctxt, Class> targetType,
+ JsonParser p, ValueDeserializer> deser, String failureMsg
+ ) throws JacksonException {
+ hitCountSecond++;
+ return "THIS IS AN ERROR";
+ }
+ }
+
+ // SUCCESS Test when problem handler was implemented as required.
+ @Test
+ public void testIssue5469HappyCase()
+ throws Exception
+ {
+ // Given
+ assertEquals(0, hitCountFirst);
+ ObjectMapper mapper = JsonMapper.builder()
+ .enable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)
+ .addHandler(new ProblemHandler5469())
+ .build();
+
+ // When
+ Person5469 person = mapper.readValue(
+ "{\"id\": \"12ab\", \"name\": \"Bob\", " +
+ // Input is NULL, but....
+ "\"age\": null}", Person5469.class);
+
+ // Then
+ assertNotNull(person);
+ assertEquals("12ab", person.id);
+ assertEquals("Bob", person.name);
+ // We get the MAGIC NUMBER as age
+ assertEquals(5469L, person.age);
+ // Sanity check, we hit the code path as we wanted
+ assertEquals(1, hitCountFirst);
+ }
+
+ // FAIL! Test when problem handler was implemented WRONG
+ @Test
+ public void testIssue5469BadImpl()
+ throws Exception
+ {
+ // Given
+ assertEquals(0, hitCountSecond);
+ ObjectMapper mapper = JsonMapper.builder()
+ .enable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)
+ .addHandler(new MoreProblemHandler5469())
+ .build();
+
+ // When
+ try {
+ mapper.readValue("{\"id\": \"12ab\", \"name\": \"Bob\", " +
+ // Input is NULL, to cause problem
+ "\"age\": null}", Person5469.class);
+ // Sanity check, we hit the code path as we wanted
+ assertEquals(1, hitCountSecond);
+ fail("Should not reach here.");
+ } catch (InvalidFormatException e) {
+ // Then
+ verifyException(e,
+ "`DeserializationProblemHandler.handleNullForPrimitives()` for type `long` returned value of type `java.lang.String`");
+ }
+ }
+}