Skip to content

Commit 8db6d1d

Browse files
authored
Fix #3690: improve error message for to-string coercion (#3717)
1 parent a5b0af8 commit 8db6d1d

File tree

9 files changed

+84
-19
lines changed

9 files changed

+84
-19
lines changed

release-notes/CREDITS-2.x

+2
Original file line numberDiff line numberDiff line change
@@ -1039,6 +1039,8 @@ João Guerra (joca-bt@github)
10391039
(2.11.1)
10401040
* Reported #3227: Content `null` handling not working for root values
10411041
(2.13.0)
1042+
* Reported #3690: Incorrect target type for arrays when disabling coercion
1043+
(2.15.0)
10421044

10431045
Ryan Bohn (bohnman@github)
10441046
* Reported #2475: `StringCollectionSerializer` calls `JsonGenerator.setCurrentValue(value)`,

release-notes/VERSION-2.x

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ Project: jackson-databind
1212
#3680: Timestamp in classes inside jar showing 02/01/1980
1313
(fix contributed by Hervé B)
1414
#3682: Transient `Field`s are not ignored as Mutators if there is visible Getter
15+
#3690: Incorrect target type for arrays when disabling coercion
16+
(reported by João G)
1517
#3708: Seems like `java.nio.file.Path` is safe for Android API level 26
1618
(contributed by @pjfanning)
1719

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

+28-6
Original file line numberDiff line numberDiff line change
@@ -1401,6 +1401,9 @@ protected final String _parseString(JsonParser p, DeserializationContext ctxt,
14011401
throws IOException
14021402
{
14031403
CoercionAction act = CoercionAction.TryConvert;
1404+
// 05-Jan-2022, tatu: We are usually coercing into `String` as element,
1405+
// or such; `_valueClass` would be wrong choice here
1406+
final Class<?> rawTargetType = String.class;
14041407

14051408
switch (p.currentTokenId()) {
14061409
case JsonTokenId.ID_STRING:
@@ -1420,17 +1423,20 @@ protected final String _parseString(JsonParser p, DeserializationContext ctxt,
14201423
case JsonTokenId.ID_START_OBJECT:
14211424
return ctxt.extractScalarFromObject(p, this, _valueClass);
14221425
case JsonTokenId.ID_NUMBER_INT:
1423-
act = _checkIntToStringCoercion(p, ctxt, _valueClass);
1426+
act = _checkIntToStringCoercion(p, ctxt, rawTargetType);
14241427
break;
14251428
case JsonTokenId.ID_NUMBER_FLOAT:
1426-
act = _checkFloatToStringCoercion(p, ctxt, _valueClass);
1429+
act = _checkFloatToStringCoercion(p, ctxt, rawTargetType);
14271430
break;
14281431
case JsonTokenId.ID_TRUE:
14291432
case JsonTokenId.ID_FALSE:
1430-
act = _checkBooleanToStringCoercion(p, ctxt, _valueClass);
1433+
act = _checkBooleanToStringCoercion(p, ctxt, rawTargetType);
14311434
break;
14321435
}
14331436

1437+
// !!! 05-Jan-2022, tatu: This might work in practice, but in theory
1438+
// we should not use "nullProvider" of this deserializer as this
1439+
// unlikely to be what we want (it's for containing type, not String)
14341440
if (act == CoercionAction.AsNull) {
14351441
return (String) nullProvider.getNullValue(ctxt);
14361442
}
@@ -1655,7 +1661,7 @@ protected CoercionAction _checkCoercionFail(DeserializationContext ctxt,
16551661
if (act == CoercionAction.Fail) {
16561662
ctxt.reportBadCoercion(this, targetType, inputValue,
16571663
"Cannot coerce %s to %s (but could if coercion was enabled using `CoercionConfig`)",
1658-
inputDesc, _coercedTypeDesc());
1664+
inputDesc, _coercedTypeDesc(targetType));
16591665
}
16601666
return act;
16611667
}
@@ -1777,8 +1783,7 @@ protected String _coercedTypeDesc() {
17771783
typeDesc = ClassUtil.getTypeDescription(t);
17781784
} else {
17791785
Class<?> cls = handledType();
1780-
structured = cls.isArray() || Collection.class.isAssignableFrom(cls)
1781-
|| Map.class.isAssignableFrom(cls);
1786+
structured = ClassUtil.isCollectionMapOrArray(cls);
17821787
typeDesc = ClassUtil.getClassDescription(cls);
17831788
}
17841789
if (structured) {
@@ -1787,6 +1792,23 @@ protected String _coercedTypeDesc() {
17871792
return typeDesc+" value";
17881793
}
17891794

1795+
/**
1796+
* Helper method called to get a description of type into which a scalar value coercion
1797+
* is being applied, to be used for constructing exception messages
1798+
* on coerce failure.
1799+
*
1800+
* @return Message with backtick-enclosed name of target type
1801+
*
1802+
* @since 2.15
1803+
*/
1804+
protected String _coercedTypeDesc(Class<?> rawTargetType) {
1805+
String typeDesc = ClassUtil.getClassDescription(rawTargetType);
1806+
if (ClassUtil.isCollectionMapOrArray(rawTargetType)) {
1807+
return "element of "+typeDesc;
1808+
}
1809+
return typeDesc+" value";
1810+
}
1811+
17901812
/*
17911813
/**********************************************************************
17921814
/* Helper methods for sub-classes, coercions, older (pre-2.12), deprecated

src/test/java/com/fasterxml/jackson/databind/convert/CoerceBoolToStringTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ public void testCoerceConfigToFail() throws JsonProcessingException
6767
{
6868
_verifyCoerceFail(MAPPER_TO_FAIL, String.class, "true");
6969
_verifyCoerceFail(MAPPER_TO_FAIL, StringWrapper.class, "{\"str\": false}", "string");
70-
_verifyCoerceFail(MAPPER_TO_FAIL, String[].class, "[ true ]", "element of `java.lang.String[]`");
70+
_verifyCoerceFail(MAPPER_TO_FAIL, String[].class, "[ true ]", "to `java.lang.String` value");
7171
}
7272

7373
/*

src/test/java/com/fasterxml/jackson/databind/convert/CoerceFloatToIntTest.java

+8-8
Original file line numberDiff line numberDiff line change
@@ -77,26 +77,26 @@ public void testLegacyFailDoubleToInt() throws Exception
7777
_verifyCoerceFail(READER_LEGACY_FAIL, Integer.class, "1.5", "java.lang.Integer");
7878
_verifyCoerceFail(READER_LEGACY_FAIL, Integer.TYPE, "1.5", "int");
7979
_verifyCoerceFail(READER_LEGACY_FAIL, IntWrapper.class, "{\"i\":-2.25 }", "int");
80-
_verifyCoerceFail(READER_LEGACY_FAIL, int[].class, "[ 2.5 ]", "element of `int[]`");
80+
_verifyCoerceFail(READER_LEGACY_FAIL, int[].class, "[ 2.5 ]", "to `int` value");
8181
}
8282

8383
public void testLegacyFailDoubleToLong() throws Exception
8484
{
8585
_verifyCoerceFail(READER_LEGACY_FAIL, Long.class, "0.5");
8686
_verifyCoerceFail(READER_LEGACY_FAIL, Long.TYPE, "-2.5");
8787
_verifyCoerceFail(READER_LEGACY_FAIL, LongWrapper.class, "{\"l\": 7.7 }");
88-
_verifyCoerceFail(READER_LEGACY_FAIL, long[].class, "[ -1.35 ]", "element of `long[]`");
88+
_verifyCoerceFail(READER_LEGACY_FAIL, long[].class, "[ -1.35 ]", "to `long` value");
8989
}
9090

9191
public void testLegacyFailDoubleToOther() throws Exception
9292
{
9393
_verifyCoerceFail(READER_LEGACY_FAIL, Short.class, "0.5");
9494
_verifyCoerceFail(READER_LEGACY_FAIL, Short.TYPE, "-2.5");
95-
_verifyCoerceFail(READER_LEGACY_FAIL, short[].class, "[ -1.35 ]", "element of `short[]`");
95+
_verifyCoerceFail(READER_LEGACY_FAIL, short[].class, "[ -1.35 ]", "to `short` value");
9696

9797
_verifyCoerceFail(READER_LEGACY_FAIL, Byte.class, "0.5");
9898
_verifyCoerceFail(READER_LEGACY_FAIL, Byte.TYPE, "-2.5");
99-
_verifyCoerceFail(READER_LEGACY_FAIL, byte[].class, "[ -1.35 ]", "element of `byte[]`");
99+
_verifyCoerceFail(READER_LEGACY_FAIL, byte[].class, "[ -1.35 ]", "to `byte` value");
100100

101101
_verifyCoerceFail(READER_LEGACY_FAIL, BigInteger.class, "25236.256");
102102

@@ -280,20 +280,20 @@ public void testCoerceConfigFailFromFloat() throws Exception
280280
_verifyCoerceFail(MAPPER_TO_FAIL, Integer.class, "1.5");
281281
_verifyCoerceFail(MAPPER_TO_FAIL, Integer.TYPE, "1.5");
282282
_verifyCoerceFail(MAPPER_TO_FAIL, IntWrapper.class, "{\"i\":-2.25 }", "int");
283-
_verifyCoerceFail(MAPPER_TO_FAIL, int[].class, "[ 2.5 ]", "element of `int[]`");
283+
_verifyCoerceFail(MAPPER_TO_FAIL, int[].class, "[ 2.5 ]", "to `int` value");
284284

285285
_verifyCoerceFail(MAPPER_TO_FAIL, Long.class, "0.5");
286286
_verifyCoerceFail(MAPPER_TO_FAIL, Long.TYPE, "-2.5");
287287
_verifyCoerceFail(MAPPER_TO_FAIL, LongWrapper.class, "{\"l\": 7.7 }");
288-
_verifyCoerceFail(MAPPER_TO_FAIL, long[].class, "[ -1.35 ]", "element of `long[]`");
288+
_verifyCoerceFail(MAPPER_TO_FAIL, long[].class, "[ -1.35 ]", "to `long` value");
289289

290290
_verifyCoerceFail(MAPPER_TO_FAIL, Short.class, "0.5");
291291
_verifyCoerceFail(MAPPER_TO_FAIL, Short.TYPE, "-2.5");
292-
_verifyCoerceFail(MAPPER_TO_FAIL, short[].class, "[ -1.35 ]", "element of `short[]`");
292+
_verifyCoerceFail(MAPPER_TO_FAIL, short[].class, "[ -1.35 ]", "to `short` value");
293293

294294
_verifyCoerceFail(MAPPER_TO_FAIL, Byte.class, "0.5");
295295
_verifyCoerceFail(MAPPER_TO_FAIL, Byte.TYPE, "-2.5");
296-
_verifyCoerceFail(MAPPER_TO_FAIL, byte[].class, "[ -1.35 ]", "element of `byte[]`");
296+
_verifyCoerceFail(MAPPER_TO_FAIL, byte[].class, "[ -1.35 ]", "to `byte` value");
297297

298298
_verifyCoerceFail(MAPPER_TO_FAIL, BigInteger.class, "25236.256");
299299
}

src/test/java/com/fasterxml/jackson/databind/convert/CoerceFloatToStringTest.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ public void testCoerceConfigToFail() throws JsonProcessingException
6868
{
6969
_verifyCoerceFail(MAPPER_TO_FAIL, String.class, "3.5");
7070
_verifyCoerceFail(MAPPER_TO_FAIL, StringWrapper.class, "{\"str\": -5.3}", "string");
71-
_verifyCoerceFail(MAPPER_TO_FAIL, String[].class, "[ 2.1 ]", "element of `java.lang.String[]`");
71+
_verifyCoerceFail(MAPPER_TO_FAIL, String[].class, "[ 2.1 ]",
72+
"to `java.lang.String` value");
7273
}
7374

7475
/*

src/test/java/com/fasterxml/jackson/databind/convert/CoerceIntToFloatTest.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -110,12 +110,12 @@ public void testCoerceConfigToFail() throws JsonProcessingException
110110
_verifyCoerceFail(MAPPER_TO_FAIL, Float.class, "3");
111111
_verifyCoerceFail(MAPPER_TO_FAIL, Float.TYPE, "-2");
112112
_verifyCoerceFail(MAPPER_TO_FAIL, FloatWrapper.class, "{\"f\": -5}", "float");
113-
_verifyCoerceFail(MAPPER_TO_FAIL, float[].class, "[ 2 ]", "element of `float[]`");
113+
_verifyCoerceFail(MAPPER_TO_FAIL, float[].class, "[ 2 ]", "to `float` value");
114114

115115
_verifyCoerceFail(MAPPER_TO_FAIL, Double.class, "-1");
116116
_verifyCoerceFail(MAPPER_TO_FAIL, Double.TYPE, "4");
117117
_verifyCoerceFail(MAPPER_TO_FAIL, DoubleWrapper.class, "{\"d\": 2}", "double");
118-
_verifyCoerceFail(MAPPER_TO_FAIL, double[].class, "[ -2 ]", "element of `double[]`");
118+
_verifyCoerceFail(MAPPER_TO_FAIL, double[].class, "[ -2 ]", "to `double` value");
119119

120120
_verifyCoerceFail(MAPPER_TO_FAIL, BigDecimal.class, "73455342");
121121
}

src/test/java/com/fasterxml/jackson/databind/convert/CoerceIntToStringTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public void testCoerceConfigToFail() throws JsonProcessingException
6868
{
6969
_verifyCoerceFail(MAPPER_TO_FAIL, String.class, "3");
7070
_verifyCoerceFail(MAPPER_TO_FAIL, StringWrapper.class, "{\"str\": -5}", "string");
71-
_verifyCoerceFail(MAPPER_TO_FAIL, String[].class, "[ 2 ]", "element of `java.lang.String[]`");
71+
_verifyCoerceFail(MAPPER_TO_FAIL, String[].class, "[ 2 ]", "to `java.lang.String` value");
7272
}
7373

7474
/*
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.fasterxml.jackson.databind.convert;
2+
3+
import java.util.List;
4+
5+
import com.fasterxml.jackson.databind.*;
6+
import com.fasterxml.jackson.databind.cfg.CoercionAction;
7+
import com.fasterxml.jackson.databind.cfg.CoercionInputShape;
8+
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
9+
10+
public class DisableCoercions3690Test extends BaseMapTest
11+
{
12+
static class Input3690 {
13+
public List<String> field;
14+
}
15+
16+
// [databind#3690]
17+
public void testFailMessage3690() throws Exception
18+
{
19+
ObjectMapper mapper = jsonMapperBuilder()
20+
.withCoercionConfigDefaults(config -> {
21+
config.setCoercion(CoercionInputShape.Boolean, CoercionAction.Fail)
22+
.setCoercion(CoercionInputShape.Integer, CoercionAction.Fail)
23+
.setCoercion(CoercionInputShape.Float, CoercionAction.Fail)
24+
.setCoercion(CoercionInputShape.String, CoercionAction.Fail)
25+
.setCoercion(CoercionInputShape.Array, CoercionAction.Fail)
26+
.setCoercion(CoercionInputShape.Object, CoercionAction.Fail);
27+
})
28+
.build();
29+
String json = "{ \"field\": [ 1 ] }";
30+
try {
31+
mapper.readValue(json, Input3690.class);
32+
fail("Should not pass");
33+
} catch (InvalidFormatException e) {
34+
verifyException(e, "Cannot coerce Integer value (1)");
35+
verifyException(e, "to `java.lang.String` value");
36+
}
37+
}
38+
}

0 commit comments

Comments
 (0)