Skip to content

Commit

Permalink
Fix #3690: improve error message for to-string coercion (#3717)
Browse files Browse the repository at this point in the history
  • Loading branch information
cowtowncoder authored Jan 6, 2023
1 parent a5b0af8 commit 8db6d1d
Show file tree
Hide file tree
Showing 9 changed files with 84 additions and 19 deletions.
2 changes: 2 additions & 0 deletions release-notes/CREDITS-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -1039,6 +1039,8 @@ João Guerra (joca-bt@github)
(2.11.1)
* Reported #3227: Content `null` handling not working for root values
(2.13.0)
* Reported #3690: Incorrect target type for arrays when disabling coercion
(2.15.0)

Ryan Bohn (bohnman@github)
* Reported #2475: `StringCollectionSerializer` calls `JsonGenerator.setCurrentValue(value)`,
Expand Down
2 changes: 2 additions & 0 deletions release-notes/VERSION-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ Project: jackson-databind
#3680: Timestamp in classes inside jar showing 02/01/1980
(fix contributed by Hervé B)
#3682: Transient `Field`s are not ignored as Mutators if there is visible Getter
#3690: Incorrect target type for arrays when disabling coercion
(reported by João G)
#3708: Seems like `java.nio.file.Path` is safe for Android API level 26
(contributed by @pjfanning)

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

switch (p.currentTokenId()) {
case JsonTokenId.ID_STRING:
Expand All @@ -1420,17 +1423,20 @@ protected final String _parseString(JsonParser p, DeserializationContext ctxt,
case JsonTokenId.ID_START_OBJECT:
return ctxt.extractScalarFromObject(p, this, _valueClass);
case JsonTokenId.ID_NUMBER_INT:
act = _checkIntToStringCoercion(p, ctxt, _valueClass);
act = _checkIntToStringCoercion(p, ctxt, rawTargetType);
break;
case JsonTokenId.ID_NUMBER_FLOAT:
act = _checkFloatToStringCoercion(p, ctxt, _valueClass);
act = _checkFloatToStringCoercion(p, ctxt, rawTargetType);
break;
case JsonTokenId.ID_TRUE:
case JsonTokenId.ID_FALSE:
act = _checkBooleanToStringCoercion(p, ctxt, _valueClass);
act = _checkBooleanToStringCoercion(p, ctxt, rawTargetType);
break;
}

// !!! 05-Jan-2022, tatu: This might work in practice, but in theory
// we should not use "nullProvider" of this deserializer as this
// unlikely to be what we want (it's for containing type, not String)
if (act == CoercionAction.AsNull) {
return (String) nullProvider.getNullValue(ctxt);
}
Expand Down Expand Up @@ -1655,7 +1661,7 @@ protected CoercionAction _checkCoercionFail(DeserializationContext ctxt,
if (act == CoercionAction.Fail) {
ctxt.reportBadCoercion(this, targetType, inputValue,
"Cannot coerce %s to %s (but could if coercion was enabled using `CoercionConfig`)",
inputDesc, _coercedTypeDesc());
inputDesc, _coercedTypeDesc(targetType));
}
return act;
}
Expand Down Expand Up @@ -1777,8 +1783,7 @@ protected String _coercedTypeDesc() {
typeDesc = ClassUtil.getTypeDescription(t);
} else {
Class<?> cls = handledType();
structured = cls.isArray() || Collection.class.isAssignableFrom(cls)
|| Map.class.isAssignableFrom(cls);
structured = ClassUtil.isCollectionMapOrArray(cls);
typeDesc = ClassUtil.getClassDescription(cls);
}
if (structured) {
Expand All @@ -1787,6 +1792,23 @@ protected String _coercedTypeDesc() {
return typeDesc+" value";
}

/**
* Helper method called to get a description of type into which a scalar value coercion
* is being applied, to be used for constructing exception messages
* on coerce failure.
*
* @return Message with backtick-enclosed name of target type
*
* @since 2.15
*/
protected String _coercedTypeDesc(Class<?> rawTargetType) {
String typeDesc = ClassUtil.getClassDescription(rawTargetType);
if (ClassUtil.isCollectionMapOrArray(rawTargetType)) {
return "element of "+typeDesc;
}
return typeDesc+" value";
}

/*
/**********************************************************************
/* Helper methods for sub-classes, coercions, older (pre-2.12), deprecated
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public void testCoerceConfigToFail() throws JsonProcessingException
{
_verifyCoerceFail(MAPPER_TO_FAIL, String.class, "true");
_verifyCoerceFail(MAPPER_TO_FAIL, StringWrapper.class, "{\"str\": false}", "string");
_verifyCoerceFail(MAPPER_TO_FAIL, String[].class, "[ true ]", "element of `java.lang.String[]`");
_verifyCoerceFail(MAPPER_TO_FAIL, String[].class, "[ true ]", "to `java.lang.String` value");
}

/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,26 +77,26 @@ public void testLegacyFailDoubleToInt() throws Exception
_verifyCoerceFail(READER_LEGACY_FAIL, Integer.class, "1.5", "java.lang.Integer");
_verifyCoerceFail(READER_LEGACY_FAIL, Integer.TYPE, "1.5", "int");
_verifyCoerceFail(READER_LEGACY_FAIL, IntWrapper.class, "{\"i\":-2.25 }", "int");
_verifyCoerceFail(READER_LEGACY_FAIL, int[].class, "[ 2.5 ]", "element of `int[]`");
_verifyCoerceFail(READER_LEGACY_FAIL, int[].class, "[ 2.5 ]", "to `int` value");
}

public void testLegacyFailDoubleToLong() throws Exception
{
_verifyCoerceFail(READER_LEGACY_FAIL, Long.class, "0.5");
_verifyCoerceFail(READER_LEGACY_FAIL, Long.TYPE, "-2.5");
_verifyCoerceFail(READER_LEGACY_FAIL, LongWrapper.class, "{\"l\": 7.7 }");
_verifyCoerceFail(READER_LEGACY_FAIL, long[].class, "[ -1.35 ]", "element of `long[]`");
_verifyCoerceFail(READER_LEGACY_FAIL, long[].class, "[ -1.35 ]", "to `long` value");
}

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

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

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

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

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

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

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

_verifyCoerceFail(MAPPER_TO_FAIL, BigInteger.class, "25236.256");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ public void testCoerceConfigToFail() throws JsonProcessingException
{
_verifyCoerceFail(MAPPER_TO_FAIL, String.class, "3.5");
_verifyCoerceFail(MAPPER_TO_FAIL, StringWrapper.class, "{\"str\": -5.3}", "string");
_verifyCoerceFail(MAPPER_TO_FAIL, String[].class, "[ 2.1 ]", "element of `java.lang.String[]`");
_verifyCoerceFail(MAPPER_TO_FAIL, String[].class, "[ 2.1 ]",
"to `java.lang.String` value");
}

/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,12 @@ public void testCoerceConfigToFail() throws JsonProcessingException
_verifyCoerceFail(MAPPER_TO_FAIL, Float.class, "3");
_verifyCoerceFail(MAPPER_TO_FAIL, Float.TYPE, "-2");
_verifyCoerceFail(MAPPER_TO_FAIL, FloatWrapper.class, "{\"f\": -5}", "float");
_verifyCoerceFail(MAPPER_TO_FAIL, float[].class, "[ 2 ]", "element of `float[]`");
_verifyCoerceFail(MAPPER_TO_FAIL, float[].class, "[ 2 ]", "to `float` value");

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

_verifyCoerceFail(MAPPER_TO_FAIL, BigDecimal.class, "73455342");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public void testCoerceConfigToFail() throws JsonProcessingException
{
_verifyCoerceFail(MAPPER_TO_FAIL, String.class, "3");
_verifyCoerceFail(MAPPER_TO_FAIL, StringWrapper.class, "{\"str\": -5}", "string");
_verifyCoerceFail(MAPPER_TO_FAIL, String[].class, "[ 2 ]", "element of `java.lang.String[]`");
_verifyCoerceFail(MAPPER_TO_FAIL, String[].class, "[ 2 ]", "to `java.lang.String` value");
}

/*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.fasterxml.jackson.databind.convert;

import java.util.List;

import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.cfg.CoercionAction;
import com.fasterxml.jackson.databind.cfg.CoercionInputShape;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;

public class DisableCoercions3690Test extends BaseMapTest
{
static class Input3690 {
public List<String> field;
}

// [databind#3690]
public void testFailMessage3690() throws Exception
{
ObjectMapper mapper = jsonMapperBuilder()
.withCoercionConfigDefaults(config -> {
config.setCoercion(CoercionInputShape.Boolean, CoercionAction.Fail)
.setCoercion(CoercionInputShape.Integer, CoercionAction.Fail)
.setCoercion(CoercionInputShape.Float, CoercionAction.Fail)
.setCoercion(CoercionInputShape.String, CoercionAction.Fail)
.setCoercion(CoercionInputShape.Array, CoercionAction.Fail)
.setCoercion(CoercionInputShape.Object, CoercionAction.Fail);
})
.build();
String json = "{ \"field\": [ 1 ] }";
try {
mapper.readValue(json, Input3690.class);
fail("Should not pass");
} catch (InvalidFormatException e) {
verifyException(e, "Cannot coerce Integer value (1)");
verifyException(e, "to `java.lang.String` value");
}
}
}

0 comments on commit 8db6d1d

Please sign in to comment.