From 0f7137ef363cddade16905105ed4041b028ad084 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Thu, 11 Dec 2025 00:16:40 +0900 Subject: [PATCH 1/7] fix #5469 --- .../databind/DeserializationContext.java | 39 ++++++++++++ .../deser/DeserializationProblemHandler.java | 33 ++++++++++ .../deser/jdk/NumberDeserializers.java | 5 +- .../databind/deser/std/StdDeserializer.java | 5 +- ...DeserializationProblemHandler5469Test.java | 61 +++++++++++++++++++ 5 files changed, 139 insertions(+), 4 deletions(-) create mode 100644 src/test/java/tools/jackson/databind/deser/filter/DeserializationProblemHandler5469Test.java diff --git a/src/main/java/tools/jackson/databind/DeserializationContext.java b/src/main/java/tools/jackson/databind/DeserializationContext.java index 6366cdd07f..75bdff44a4 100644 --- a/src/main/java/tools/jackson/databind/DeserializationContext.java +++ b/src/main/java/tools/jackson/databind/DeserializationContext.java @@ -1354,6 +1354,45 @@ public Object handleWeirdStringValue(Class targetClass, String value, throw weirdStringException(value, targetClass, msg); } + /** + * 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 DeserializationContext#reportInputMismatch(Class, String, Object...)}, + * which by default would throw {@link MismatchedInputException} + * + * @param targetClass Type of property into which incoming String should be converted + * @param deser Type of {@link ValueDeserializer} calling this method. + * @param msg Error message template caller wants to use if exception is to be thrown + * @param msgArgs Optional arguments to use for message, if any + * + * @throws JacksonException To indicate unrecoverable problem, usually based on msg + */ + public Object handleNullForPrimitives(Class targetClass, + ValueDeserializer deser, String msg, Object... msgArgs) + throws JacksonException + + { + // but if not handled, just throw exception + msg = _format(msg, msgArgs); + LinkedNode h = _config.getProblemHandlers(); + while (h != null) { + // Can bail out if it's handled + Object instance = h.value().handleNullForPrimitives(this, deser, msg); + if (instance != DeserializationProblemHandler.NOT_HANDLED) { + // Sanity check for broken handlers, otherwise nasty to debug: + if (_isCompatible(targetClass, instance)) { + return instance; + } + return reportInputMismatch(deser, + "Cannot map `null` into type %s (set DeserializationConfig.DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES to 'false' to allow)"); + } + h = h.next(); + } + return reportInputMismatch(deser, + "Cannot map `null` into type %s (set DeserializationConfig.DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES to 'false' to allow)"); + } + /** * Method that deserializers should call if they encounter a numeric value * that cannot be converted to target property type, in cases where some diff --git a/src/main/java/tools/jackson/databind/deser/DeserializationProblemHandler.java b/src/main/java/tools/jackson/databind/deser/DeserializationProblemHandler.java index 8cd736b745..0399f5b22d 100644 --- a/src/main/java/tools/jackson/databind/deser/DeserializationProblemHandler.java +++ b/src/main/java/tools/jackson/databind/deser/DeserializationProblemHandler.java @@ -7,6 +7,7 @@ import tools.jackson.core.JsonToken; import tools.jackson.databind.*; import tools.jackson.databind.jsontype.TypeIdResolver; +import tools.jackson.databind.util.ClassUtil; /** * This is the class that can be registered (via @@ -221,6 +222,38 @@ public Object handleUnexpectedToken(DeserializationContext ctxt, return NOT_HANDLED; } + /** + * Method that deserializers should call if the {@code null} value is encountered and + * need to deserialize as a Primitive types (e.g. int, long etc...) that is, type of token that deserializer + * cannot handle). This could occur, for example, if a Number deserializer + * encounter {@link JsonToken#START_ARRAY} instead of + * {@link JsonToken#VALUE_NUMBER_INT} or {@link JsonToken#VALUE_NUMBER_FLOAT}. + * + * + * @param failureMsg Message that will be used by caller + * to indicate type of failure unless handler produces 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, + 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..dada3563cd 100644 --- a/src/main/java/tools/jackson/databind/deser/jdk/NumberDeserializers.java +++ b/src/main/java/tools/jackson/databind/deser/jdk/NumberDeserializers.java @@ -159,13 +159,14 @@ 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)", + return (T) ctxt.handleNullForPrimitives(handledType(), this, "Cannot map `null` into type %s (set DeserializationConfig.DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES to 'false' to allow)", + // Fix ClassUtil.classNameOf(handledType())); } return _nullValue; 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 fbee204ca5..965b245c42 100644 --- a/src/main/java/tools/jackson/databind/deser/std/StdDeserializer.java +++ b/src/main/java/tools/jackson/databind/deser/std/StdDeserializer.java @@ -1638,8 +1638,9 @@ protected final void _verifyNullForPrimitive(DeserializationContext ctxt) throws DatabindException { if (ctxt.isEnabled(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)) { - ctxt.reportInputMismatch(this, -"Cannot coerce `null` to %s (disable `DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES` to allow)", + ctxt.handleNullForPrimitives(handledType(), + this, + "Cannot coerce `null` to %s (disable `DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES` to allow)", _coercedTypeDesc()); } } 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..785d7fe64d --- /dev/null +++ b/src/test/java/tools/jackson/databind/deser/filter/DeserializationProblemHandler5469Test.java @@ -0,0 +1,61 @@ +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.core.JsonToken; +import tools.jackson.databind.*; +import tools.jackson.databind.deser.DeserializationProblemHandler; +import tools.jackson.databind.json.JsonMapper; +import tools.jackson.databind.testutil.DatabindTestUtil; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +// For [databind#5469] Add callback to signal null for primitive in DeserializationProblemHandler +public class DeserializationProblemHandler5469Test + extends DatabindTestUtil +{ + private static int hitCount = 0; + static class Person5469 { + public String id; + public String name; + public long age; + } + + static class ProblemHandler5469 extends DeserializationProblemHandler + { + @Override + public Object handleNullForPrimitives(DeserializationContext ctxt, ValueDeserializer deser, String failureMsg) throws JacksonException { + hitCount++; + return 5469L; + } + } + + @Test + public void testIssue5469() + throws Exception + { + // Given + assertEquals(0, hitCount); + 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, hitCount); + } +} From a7ea15d65fb5bdae066a03f153a1ee6970c5f5e4 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Thu, 11 Dec 2025 00:21:00 +0900 Subject: [PATCH 2/7] fix msg 5469 --- .../java/tools/jackson/databind/DeserializationContext.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/java/tools/jackson/databind/DeserializationContext.java b/src/main/java/tools/jackson/databind/DeserializationContext.java index 75bdff44a4..9e87d4fba3 100644 --- a/src/main/java/tools/jackson/databind/DeserializationContext.java +++ b/src/main/java/tools/jackson/databind/DeserializationContext.java @@ -1384,13 +1384,11 @@ public Object handleNullForPrimitives(Class targetClass, if (_isCompatible(targetClass, instance)) { return instance; } - return reportInputMismatch(deser, - "Cannot map `null` into type %s (set DeserializationConfig.DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES to 'false' to allow)"); + return reportInputMismatch(deser, msg); } h = h.next(); } - return reportInputMismatch(deser, - "Cannot map `null` into type %s (set DeserializationConfig.DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES to 'false' to allow)"); + return reportInputMismatch(deser, msg); } /** From 04d6392ab6c5347d40db605e4a8effad67d03445 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Thu, 11 Dec 2025 01:12:46 +0900 Subject: [PATCH 3/7] Remove msgArgs --- .../java/tools/jackson/databind/DeserializationContext.java | 4 +--- .../jackson/databind/deser/jdk/NumberDeserializers.java | 6 +++--- .../tools/jackson/databind/deser/std/StdDeserializer.java | 4 ++-- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/main/java/tools/jackson/databind/DeserializationContext.java b/src/main/java/tools/jackson/databind/DeserializationContext.java index 9e87d4fba3..ad6dce299a 100644 --- a/src/main/java/tools/jackson/databind/DeserializationContext.java +++ b/src/main/java/tools/jackson/databind/DeserializationContext.java @@ -1364,17 +1364,15 @@ public Object handleWeirdStringValue(Class targetClass, String value, * @param targetClass Type of property into which incoming String should be converted * @param deser Type of {@link ValueDeserializer} calling this method. * @param msg Error message template caller wants to use if exception is to be thrown - * @param msgArgs Optional arguments to use for message, if any * * @throws JacksonException To indicate unrecoverable problem, usually based on msg */ public Object handleNullForPrimitives(Class targetClass, - ValueDeserializer deser, String msg, Object... msgArgs) + ValueDeserializer deser, String msg) throws JacksonException { // but if not handled, just throw exception - msg = _format(msg, msgArgs); LinkedNode h = _config.getProblemHandlers(); while (h != null) { // Can bail out if it's handled 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 dada3563cd..4115e63b2a 100644 --- a/src/main/java/tools/jackson/databind/deser/jdk/NumberDeserializers.java +++ b/src/main/java/tools/jackson/databind/deser/jdk/NumberDeserializers.java @@ -165,9 +165,9 @@ 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)) { - return (T) ctxt.handleNullForPrimitives(handledType(), this, "Cannot map `null` into type %s (set DeserializationConfig.DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES to 'false' to allow)", - // Fix - ClassUtil.classNameOf(handledType())); + return (T) ctxt.handleNullForPrimitives(handledType(), this, + String.format("Cannot map `null` into type %s (set DeserializationConfig.DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES to 'false' to allow)", + ClassUtil.classNameOf(handledType()))); } return _nullValue; } 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 965b245c42..63126c0395 100644 --- a/src/main/java/tools/jackson/databind/deser/std/StdDeserializer.java +++ b/src/main/java/tools/jackson/databind/deser/std/StdDeserializer.java @@ -1640,8 +1640,8 @@ protected final void _verifyNullForPrimitive(DeserializationContext ctxt) if (ctxt.isEnabled(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)) { ctxt.handleNullForPrimitives(handledType(), this, - "Cannot coerce `null` to %s (disable `DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES` to allow)", - _coercedTypeDesc()); + String.format("Cannot coerce `null` to %s (disable `DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES` to allow)", + _coercedTypeDesc())); } } From af830964d5509510195c2a7b9f184e7c9bbdf3ca Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Thu, 11 Dec 2025 01:14:33 +0900 Subject: [PATCH 4/7] Fix JavaDoc --- .../java/tools/jackson/databind/DeserializationContext.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/tools/jackson/databind/DeserializationContext.java b/src/main/java/tools/jackson/databind/DeserializationContext.java index ad6dce299a..696eebfc5b 100644 --- a/src/main/java/tools/jackson/databind/DeserializationContext.java +++ b/src/main/java/tools/jackson/databind/DeserializationContext.java @@ -1361,8 +1361,8 @@ public Object handleWeirdStringValue(Class targetClass, String value, * Default implementation will try to call {@link DeserializationContext#reportInputMismatch(Class, String, Object...)}, * which by default would throw {@link MismatchedInputException} * - * @param targetClass Type of property into which incoming String should be converted - * @param deser Type of {@link ValueDeserializer} calling this method. + * @param targetClass Primitive type into which incoming {@code null} value should be converted to + * @param deser Type of {@link ValueDeserializer} calling this method * @param msg Error message template caller wants to use if exception is to be thrown * * @throws JacksonException To indicate unrecoverable problem, usually based on msg From d62274c80144af75a9667d05523b2a8d8f859316 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Thu, 11 Dec 2025 21:26:23 +0900 Subject: [PATCH 5/7] Add target type to signature --- .../tools/jackson/databind/DeserializationContext.java | 2 +- .../databind/deser/DeserializationProblemHandler.java | 10 +++++++--- .../databind/deser/jdk/NumberDeserializers.java | 3 ++- .../filter/DeserializationProblemHandler5469Test.java | 3 ++- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/main/java/tools/jackson/databind/DeserializationContext.java b/src/main/java/tools/jackson/databind/DeserializationContext.java index 696eebfc5b..8fbe3e9c19 100644 --- a/src/main/java/tools/jackson/databind/DeserializationContext.java +++ b/src/main/java/tools/jackson/databind/DeserializationContext.java @@ -1376,7 +1376,7 @@ public Object handleNullForPrimitives(Class targetClass, LinkedNode h = _config.getProblemHandlers(); while (h != null) { // Can bail out if it's handled - Object instance = h.value().handleNullForPrimitives(this, deser, msg); + Object instance = h.value().handleNullForPrimitives(this, targetClass, deser, msg); if (instance != DeserializationProblemHandler.NOT_HANDLED) { // Sanity check for broken handlers, otherwise nasty to debug: if (_isCompatible(targetClass, instance)) { diff --git a/src/main/java/tools/jackson/databind/deser/DeserializationProblemHandler.java b/src/main/java/tools/jackson/databind/deser/DeserializationProblemHandler.java index 0399f5b22d..1b7934729c 100644 --- a/src/main/java/tools/jackson/databind/deser/DeserializationProblemHandler.java +++ b/src/main/java/tools/jackson/databind/deser/DeserializationProblemHandler.java @@ -240,15 +240,19 @@ public Object handleUnexpectedToken(DeserializationContext ctxt, * * * - * @param failureMsg Message that will be used by caller - * to indicate type of failure unless handler produces value to use + * @param ctxt + * @param targetType Target type to deserialize into + * @param deser Target deserializer that attempted to deserialize {@code null} value in question + * @param failureMsg Message that will be used by caller to indicate type of failure unless + * handler produces 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, - ValueDeserializer deser, String failureMsg) + Class targetType, ValueDeserializer deser, String failureMsg) throws JacksonException { return NOT_HANDLED; 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 4115e63b2a..8f6eabcea5 100644 --- a/src/main/java/tools/jackson/databind/deser/jdk/NumberDeserializers.java +++ b/src/main/java/tools/jackson/databind/deser/jdk/NumberDeserializers.java @@ -167,7 +167,8 @@ public final T getNullValue(DeserializationContext ctxt) { if (_primitive && ctxt.isEnabled(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)) { return (T) ctxt.handleNullForPrimitives(handledType(), this, String.format("Cannot map `null` into type %s (set DeserializationConfig.DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES to 'false' to allow)", - ClassUtil.classNameOf(handledType()))); + ClassUtil.classNameOf(handledType())) + ); } return _nullValue; } diff --git a/src/test/java/tools/jackson/databind/deser/filter/DeserializationProblemHandler5469Test.java b/src/test/java/tools/jackson/databind/deser/filter/DeserializationProblemHandler5469Test.java index 785d7fe64d..db04b56bb3 100644 --- a/src/test/java/tools/jackson/databind/deser/filter/DeserializationProblemHandler5469Test.java +++ b/src/test/java/tools/jackson/databind/deser/filter/DeserializationProblemHandler5469Test.java @@ -26,10 +26,11 @@ static class Person5469 { static class ProblemHandler5469 extends DeserializationProblemHandler { @Override - public Object handleNullForPrimitives(DeserializationContext ctxt, ValueDeserializer deser, String failureMsg) throws JacksonException { + public Object handleNullForPrimitives(DeserializationContext ctxt, Class targetType, ValueDeserializer deser, String failureMsg) throws JacksonException { hitCount++; return 5469L; } + } @Test From a010efa041624c62a95212a60b108cd48d2d45a3 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Thu, 11 Dec 2025 21:41:16 +0900 Subject: [PATCH 6/7] [FX-5469] Add more context --- .../tools/jackson/databind/DeserializationContext.java | 8 ++++++-- .../databind/deser/DeserializationProblemHandler.java | 3 ++- .../filter/DeserializationProblemHandler5469Test.java | 4 +++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main/java/tools/jackson/databind/DeserializationContext.java b/src/main/java/tools/jackson/databind/DeserializationContext.java index 8fbe3e9c19..769366315b 100644 --- a/src/main/java/tools/jackson/databind/DeserializationContext.java +++ b/src/main/java/tools/jackson/databind/DeserializationContext.java @@ -1376,13 +1376,17 @@ public Object handleNullForPrimitives(Class targetClass, LinkedNode h = _config.getProblemHandlers(); while (h != null) { // Can bail out if it's handled - Object instance = h.value().handleNullForPrimitives(this, targetClass, deser, msg); + Object instance = h.value().handleNullForPrimitives(this, targetClass, deser, _parser, msg); if (instance != DeserializationProblemHandler.NOT_HANDLED) { // Sanity check for broken handlers, otherwise nasty to debug: if (_isCompatible(targetClass, instance)) { return instance; } - return reportInputMismatch(deser, msg); + // In case our problem handler providing incompatible value, + throw InvalidFormatException.from(_parser, + String.format("Cannot deserialize value of type %s from number %s: %s", + targetClass, ClassUtil.getClassDescription(instance), msg) + ); } h = h.next(); } diff --git a/src/main/java/tools/jackson/databind/deser/DeserializationProblemHandler.java b/src/main/java/tools/jackson/databind/deser/DeserializationProblemHandler.java index 1b7934729c..fa84a284de 100644 --- a/src/main/java/tools/jackson/databind/deser/DeserializationProblemHandler.java +++ b/src/main/java/tools/jackson/databind/deser/DeserializationProblemHandler.java @@ -243,6 +243,7 @@ public Object handleUnexpectedToken(DeserializationContext ctxt, * @param ctxt * @param targetType Target type to deserialize into * @param deser Target deserializer that attempted to deserialize {@code null} value in question + * @param p JsonParser used * @param failureMsg Message that will be used by caller to indicate type of failure unless * handler produces value to use * @@ -252,7 +253,7 @@ public Object handleUnexpectedToken(DeserializationContext ctxt, * null */ public Object handleNullForPrimitives(DeserializationContext ctxt, - Class targetType, ValueDeserializer deser, String failureMsg) + Class targetType, ValueDeserializer deser, JsonParser p, String failureMsg) throws JacksonException { return NOT_HANDLED; diff --git a/src/test/java/tools/jackson/databind/deser/filter/DeserializationProblemHandler5469Test.java b/src/test/java/tools/jackson/databind/deser/filter/DeserializationProblemHandler5469Test.java index db04b56bb3..e0f652d55a 100644 --- a/src/test/java/tools/jackson/databind/deser/filter/DeserializationProblemHandler5469Test.java +++ b/src/test/java/tools/jackson/databind/deser/filter/DeserializationProblemHandler5469Test.java @@ -26,7 +26,9 @@ static class Person5469 { static class ProblemHandler5469 extends DeserializationProblemHandler { @Override - public Object handleNullForPrimitives(DeserializationContext ctxt, Class targetType, ValueDeserializer deser, String failureMsg) throws JacksonException { + public Object handleNullForPrimitives(DeserializationContext ctxt, Class targetType, + ValueDeserializer deser, JsonParser p, String failureMsg + ) throws JacksonException { hitCount++; return 5469L; } From dfed7eab887ceca653f24496bc37c30857d6d8c5 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Thu, 11 Dec 2025 21:51:40 +0900 Subject: [PATCH 7/7] Change error message AND handler --- .../databind/DeserializationContext.java | 8 +-- ...DeserializationProblemHandler5469Test.java | 56 ++++++++++++++++--- 2 files changed, 51 insertions(+), 13 deletions(-) diff --git a/src/main/java/tools/jackson/databind/DeserializationContext.java b/src/main/java/tools/jackson/databind/DeserializationContext.java index 769366315b..0aa36279ab 100644 --- a/src/main/java/tools/jackson/databind/DeserializationContext.java +++ b/src/main/java/tools/jackson/databind/DeserializationContext.java @@ -1370,7 +1370,6 @@ public Object handleWeirdStringValue(Class targetClass, String value, public Object handleNullForPrimitives(Class targetClass, ValueDeserializer deser, String msg) throws JacksonException - { // but if not handled, just throw exception LinkedNode h = _config.getProblemHandlers(); @@ -1383,9 +1382,10 @@ public Object handleNullForPrimitives(Class targetClass, return instance; } // In case our problem handler providing incompatible value, - throw InvalidFormatException.from(_parser, - String.format("Cannot deserialize value of type %s from number %s: %s", - targetClass, ClassUtil.getClassDescription(instance), msg) + throw new InvalidFormatException(_parser, + String.format("Cannot deserialize value of type %s from type %s", + targetClass, ClassUtil.getClassDescription(instance)), + instance, targetClass ); } h = h.next(); diff --git a/src/test/java/tools/jackson/databind/deser/filter/DeserializationProblemHandler5469Test.java b/src/test/java/tools/jackson/databind/deser/filter/DeserializationProblemHandler5469Test.java index e0f652d55a..d68e40ce8e 100644 --- a/src/test/java/tools/jackson/databind/deser/filter/DeserializationProblemHandler5469Test.java +++ b/src/test/java/tools/jackson/databind/deser/filter/DeserializationProblemHandler5469Test.java @@ -3,44 +3,58 @@ import org.junit.jupiter.api.Test; import tools.jackson.core.JacksonException; import tools.jackson.core.JsonParser; -import tools.jackson.core.JsonToken; 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.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.*; // For [databind#5469] Add callback to signal null for primitive in DeserializationProblemHandler public class DeserializationProblemHandler5469Test extends DatabindTestUtil { - private static int hitCount = 0; + 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, ValueDeserializer deser, JsonParser p, String failureMsg ) throws JacksonException { - hitCount++; + hitCountFirst++; return 5469L; } } + private static int hitCountSecond = 0; + static class MoreProblemHandler5469 extends DeserializationProblemHandler + { + @Override + public Object handleNullForPrimitives(DeserializationContext ctxt, Class targetType, + ValueDeserializer deser, JsonParser p, String failureMsg + ) throws JacksonException { + hitCountFirst++; + return "THIS IS AN ERROR"; + } + + } + + // SUCCESS Test when problem handler was implemented as required. @Test - public void testIssue5469() - throws Exception + public void testIssue5469HappyCase() + throws Exception { // Given - assertEquals(0, hitCount); + assertEquals(0, hitCountFirst); ObjectMapper mapper = JsonMapper.builder() .enable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES) .addHandler(new ProblemHandler5469()) @@ -59,6 +73,30 @@ public void testIssue5469() // We get the MAGIC NUMBER as age assertEquals(5469L, person.age); // Sanity check, we hit the code path as we wanted - assertEquals(1, hitCount); + assertEquals(1, hitCountFirst); + } + + // FAIL! Test when problem handler was implemented WRONG + @Test + public void testIssue5469BadImpl() + throws Exception + { + // Given + assertEquals(0, hitCountFirst); + 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 problme + "\"age\": null}", Person5469.class); + fail("Should not reach here."); + } catch (InvalidFormatException e) { + // Then + verifyException(e, "Cannot deserialize value of type long from type `java.lang.String`"); + } } }