Skip to content

Commit c3b7946

Browse files
authored
Fix #4656: prevent ClassCastException from DeserializationProblemHandler.handleUnexpectedToken() (#5164)
1 parent 171fb41 commit c3b7946

File tree

8 files changed

+148
-46
lines changed

8 files changed

+148
-46
lines changed

release-notes/VERSION-2.x

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ Project: jackson-databind
1212
(contributed by Giulio L)
1313
#4136: Drop deprecated (in 2.12) `PropertyNamingStrategy` implementations
1414
from 2.20
15+
#4656: `DeserializationProblemHandler.handleUnexpectedToken()` cast Object to String
16+
(reported by @yacine-pc)
1517
#5103: Use `writeStartObject(Object forValue, int size)` for `ObjectNode`
1618
serialization
1719
#5151: Add new exception type, `MissingInjectValueException`, to be used

src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -968,17 +968,22 @@ public Calendar constructCalendar(Date d) {
968968
* Method to call in case incoming shape is Object Value (and parser thereby
969969
* points to {@link com.fasterxml.jackson.core.JsonToken#START_OBJECT} token),
970970
* but a Scalar value (potentially coercible from String value) is expected.
971-
* This would typically be used to deserializer a Number, Boolean value or some other
971+
* This would typically be used to deserialize a Number, Boolean value or some other
972972
* "simple" unstructured value type.
973+
*<p>
974+
* Note that expected behavior in case of extraction not succeeding changed in
975+
* Jackson 2.20: now {@code null} is expected to be returned in that case (in 2.19
976+
* and before, exception was to be thrown, but this prevented fallback handling
977+
* via {@link DeserializationProblemHandler#handleUnexpectedToken}.
973978
*
974979
* @param p Actual parser to read content from
975980
* @param deser Deserializer that needs extracted String value
976981
* @param scalarType Immediate type of scalar to extract; usually type deserializer
977982
* handles but not always (for example, deserializer for {@code int[]} would pass
978983
* scalar type of {@code int})
979984
*
980-
* @return String value found; not {@code null} (exception should be thrown if no suitable
981-
* value found)
985+
* @return String value found, if any; {@code null} if none (note: changed in 2.20;
986+
* before that in 2.19 and before exception throwing was expected)
982987
*
983988
* @throws IOException If there are problems either reading content (underlying parser
984989
* problem) or finding expected scalar value
@@ -987,7 +992,10 @@ public String extractScalarFromObject(JsonParser p, JsonDeserializer<?> deser,
987992
Class<?> scalarType)
988993
throws IOException
989994
{
990-
return (String) handleUnexpectedToken(scalarType, p);
995+
// 17-May-2025, tatu: [databind#4656] must NOT call `handleUnexpectedToken`
996+
// since that can return value other than {@code String}
997+
//return (String) handleUnexpectedToken(scalarType, p);
998+
return null;
991999
}
9921000

9931001
/*

src/main/java/com/fasterxml/jackson/databind/deser/std/EnumDeserializer.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -285,8 +285,12 @@ public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOEx
285285
}
286286
// 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
287287
if (p.isExpectedStartObjectToken()) {
288-
return _fromString(p, ctxt,
289-
ctxt.extractScalarFromObject(p, this, _valueClass));
288+
String str = ctxt.extractScalarFromObject(p, this, _valueClass);
289+
// 17-May-2025, tatu: [databind#4656] need to check for `null`
290+
if (str == null) {
291+
return ctxt.handleUnexpectedToken(_enumClass(), p);
292+
}
293+
return _fromString(p, ctxt, str);
290294
}
291295
return _deserializeOther(p, ctxt);
292296
}

src/main/java/com/fasterxml/jackson/databind/deser/std/FactoryBasedEnumDeserializer.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,10 @@ public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOEx
190190
// https://github.com/FasterXML/jackson-databind/issues/4807
191191
if (t == JsonToken.START_OBJECT) {
192192
value = ctxt.extractScalarFromObject(p, this, _valueClass);
193+
// 17-May-2025, tatu: [databind#4656] need to check for `null`
194+
if (value == null) {
195+
return ctxt.handleUnexpectedToken(_valueClass, p);
196+
}
193197
} else if ((t == null) || !t.isScalarValue()) {
194198
// Could argue we should throw an exception but...
195199
// 01-Jun-2023, tatu: And now we will finally do it!

src/main/java/com/fasterxml/jackson/databind/deser/std/FromStringDeserializer.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,10 @@ public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOExcepti
154154
}
155155
// 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
156156
text = ctxt.extractScalarFromObject(p, this, _valueClass);
157+
// 17-May-2025, tatu: [databind#4656] need to check for `null`
158+
if (text == null) {
159+
return (T) ctxt.handleUnexpectedToken(_valueClass, p);
160+
}
157161
}
158162
if (text.isEmpty()) {
159163
// 09-Jun-2020, tatu: Commonly `null` but may coerce to "empty" as well

src/main/java/com/fasterxml/jackson/databind/deser/std/NumberDeserializers.java

Lines changed: 54 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,11 @@ protected Byte _parseByte(JsonParser p, DeserializationContext ctxt)
301301
// 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
302302
case JsonTokenId.ID_START_OBJECT:
303303
text = ctxt.extractScalarFromObject(p, this, _valueClass);
304-
break;
304+
// 17-May-2025, tatu: [databind#4656] need to check for `null`
305+
if (text != null) {
306+
break;
307+
}
308+
// fall through
305309
default:
306310
return (Byte) ctxt.handleUnexpectedToken(getValueType(ctxt), p);
307311
}
@@ -384,12 +388,16 @@ protected Short _parseShort(JsonParser p, DeserializationContext ctxt)
384388
return (Short) getNullValue(ctxt);
385389
case JsonTokenId.ID_NUMBER_INT:
386390
return p.getShortValue();
391+
case JsonTokenId.ID_START_ARRAY:
392+
return (Short)_deserializeFromArray(p, ctxt);
387393
// 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
388394
case JsonTokenId.ID_START_OBJECT:
395+
// 17-May-2025, tatu: [databind#4656] need to check for `null`
389396
text = ctxt.extractScalarFromObject(p, this, _valueClass);
390-
break;
391-
case JsonTokenId.ID_START_ARRAY:
392-
return (Short)_deserializeFromArray(p, ctxt);
397+
if (text != null) {
398+
break;
399+
}
400+
// fall through
393401
default:
394402
return (Short) ctxt.handleUnexpectedToken(getValueType(ctxt), p);
395403
}
@@ -474,12 +482,16 @@ public Character deserialize(JsonParser p, DeserializationContext ctxt)
474482
_verifyNullForPrimitive(ctxt);
475483
}
476484
return (Character) getNullValue(ctxt);
485+
case JsonTokenId.ID_START_ARRAY:
486+
return _deserializeFromArray(p, ctxt);
477487
// 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
478488
case JsonTokenId.ID_START_OBJECT:
479489
text = ctxt.extractScalarFromObject(p, this, _valueClass);
480-
break;
481-
case JsonTokenId.ID_START_ARRAY:
482-
return _deserializeFromArray(p, ctxt);
490+
// 17-May-2025, tatu: [databind#4656] need to check for `null`
491+
if (text != null) {
492+
break;
493+
}
494+
// fall through
483495
default:
484496
return (Character) ctxt.handleUnexpectedToken(getValueType(ctxt), p);
485497
}
@@ -622,12 +634,16 @@ protected final Float _parseFloat(JsonParser p, DeserializationContext ctxt)
622634
// fall through to coerce
623635
case JsonTokenId.ID_NUMBER_FLOAT:
624636
return p.getFloatValue();
637+
case JsonTokenId.ID_START_ARRAY:
638+
return _deserializeFromArray(p, ctxt);
625639
// 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
626640
case JsonTokenId.ID_START_OBJECT:
627641
text = ctxt.extractScalarFromObject(p, this, _valueClass);
628-
break;
629-
case JsonTokenId.ID_START_ARRAY:
630-
return _deserializeFromArray(p, ctxt);
642+
// 17-May-2025, tatu: [databind#4656] need to check for `null`
643+
if (text != null) {
644+
break;
645+
}
646+
// fall through
631647
default:
632648
return (Float) ctxt.handleUnexpectedToken(getValueType(ctxt), p);
633649
}
@@ -723,12 +739,16 @@ protected final Double _parseDouble(JsonParser p, DeserializationContext ctxt) t
723739
// fall through to coerce
724740
case JsonTokenId.ID_NUMBER_FLOAT: // safe coercion
725741
return p.getDoubleValue();
742+
case JsonTokenId.ID_START_ARRAY:
743+
return _deserializeFromArray(p, ctxt);
726744
// 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
727745
case JsonTokenId.ID_START_OBJECT:
728746
text = ctxt.extractScalarFromObject(p, this, _valueClass);
729-
break;
730-
case JsonTokenId.ID_START_ARRAY:
731-
return _deserializeFromArray(p, ctxt);
747+
// 17-May-2025, tatu: [databind#4656] need to check for `null`
748+
if (text != null) {
749+
break;
750+
}
751+
// fall through
732752
default:
733753
return (Double) ctxt.handleUnexpectedToken(getValueType(ctxt), p);
734754
}
@@ -816,12 +836,16 @@ public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOEx
816836
}
817837
}
818838
return p.getNumberValue();
839+
case JsonTokenId.ID_START_ARRAY:
840+
return _deserializeFromArray(p, ctxt);
819841
// 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
820842
case JsonTokenId.ID_START_OBJECT:
821843
text = ctxt.extractScalarFromObject(p, this, _valueClass);
822-
break;
823-
case JsonTokenId.ID_START_ARRAY:
824-
return _deserializeFromArray(p, ctxt);
844+
// 17-May-2025, tatu: [databind#4656] need to check for `null`
845+
if (text != null) {
846+
break;
847+
}
848+
// fall through
825849
default:
826850
return ctxt.handleUnexpectedToken(getValueType(ctxt), p);
827851
}
@@ -951,12 +975,16 @@ public BigInteger deserialize(JsonParser p, DeserializationContext ctxt) throws
951975
final BigDecimal bd = p.getDecimalValue();
952976
p.streamReadConstraints().validateBigIntegerScale(bd.scale());
953977
return bd.toBigInteger();
978+
case JsonTokenId.ID_START_ARRAY:
979+
return _deserializeFromArray(p, ctxt);
954980
// 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
955981
case JsonTokenId.ID_START_OBJECT:
956982
text = ctxt.extractScalarFromObject(p, this, _valueClass);
957-
break;
958-
case JsonTokenId.ID_START_ARRAY:
959-
return _deserializeFromArray(p, ctxt);
983+
// 17-May-2025, tatu: [databind#4656] need to check for `null`
984+
if (text != null) {
985+
break;
986+
}
987+
// fall through
960988
default:
961989
// String is ok too, can easily convert; otherwise, no can do:
962990
return (BigInteger) ctxt.handleUnexpectedToken(getValueType(ctxt), p);
@@ -1024,12 +1052,16 @@ public BigDecimal deserialize(JsonParser p, DeserializationContext ctxt)
10241052
case JsonTokenId.ID_STRING:
10251053
text = p.getText();
10261054
break;
1055+
case JsonTokenId.ID_START_ARRAY:
1056+
return _deserializeFromArray(p, ctxt);
10271057
// 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
10281058
case JsonTokenId.ID_START_OBJECT:
10291059
text = ctxt.extractScalarFromObject(p, this, _valueClass);
1030-
break;
1031-
case JsonTokenId.ID_START_ARRAY:
1032-
return _deserializeFromArray(p, ctxt);
1060+
// 17-May-2025, tatu: [databind#4656] need to check for `null`
1061+
if (text != null) {
1062+
break;
1063+
}
1064+
// fall through
10331065
default:
10341066
return (BigDecimal) ctxt.handleUnexpectedToken(getValueType(ctxt), p);
10351067
}

0 commit comments

Comments
 (0)