Skip to content

Commit

Permalink
Merge pull request FasterXML#70 from dansanduleac/ds/fix-coerce-after…
Browse files Browse the repository at this point in the history
…burner

Only coerce scalars if ALLOW_COERCION_OF_SCALARS is enabled
  • Loading branch information
cowtowncoder authored Jan 28, 2019
2 parents fee2c71 + eb6c6a4 commit e315619
Show file tree
Hide file tree
Showing 2 changed files with 213 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ protected final boolean _deserializeBoolean(JsonParser p, DeserializationContext
}

if (t == JsonToken.VALUE_NUMBER_INT) {
_verifyScalarCoercion(ctxt, p, "boolean");
// 11-Jan-2012, tatus: May be outside of int...
if (p.getNumberType() == NumberType.INT) {
return (p.getIntValue() != 0);
Expand All @@ -194,6 +195,7 @@ protected final boolean _deserializeBoolean(JsonParser p, DeserializationContext
}
// And finally, let's allow Strings to be converted too
if (t == JsonToken.VALUE_STRING) {
_verifyScalarCoercion(ctxt, p, "boolean");
String text = p.getText().trim();
if ("true".equals(text) || "True".equals(text)) {
return true;
Expand Down Expand Up @@ -240,6 +242,7 @@ protected final int _deserializeInt(JsonParser p, DeserializationContext ctxt)
}
JsonToken t = p.getCurrentToken();
if (t == JsonToken.VALUE_STRING) { // let's do implicit re-parse
_verifyScalarCoercion(ctxt, p, "int");
String text = p.getText().trim();
if (_hasTextualNull(text)) {
return 0;
Expand Down Expand Up @@ -299,6 +302,7 @@ protected final long _deserializeLong(JsonParser p, DeserializationContext ctxt)
}
return p.getValueAsLong();
case JsonTokenId.ID_STRING:
_verifyScalarCoercion(ctxt, p, "long");
String text = p.getText().trim();
if (text.length() == 0 || _hasTextualNull(text)) {
return 0L;
Expand Down Expand Up @@ -395,6 +399,19 @@ protected void _failDoubleToIntCoercion(JsonParser p, DeserializationContext ctx
p.getValueAsString(), type);
}

private void _verifyScalarCoercion(DeserializationContext ctxt, JsonParser parser, String type) throws IOException {
MapperFeature feat = MapperFeature.ALLOW_COERCION_OF_SCALARS;
if (!ctxt.isEnabled(feat)) {
ctxt.reportInputMismatch(getType(),
"Cannot coerce JSON %s value (%s) into %s (enable `%s.%s` to allow)",
parser.currentToken().name(),
parser.readValueAsTree(),
type,
feat.getClass().getSimpleName(),
feat.name());
}
}

protected boolean _hasTextualNull(String value) {
return "null".equals(value);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
package com.fasterxml.jackson.module.afterburner.deser.struct;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
import com.fasterxml.jackson.module.afterburner.AfterburnerTestBase;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;

// for [databind#1106], [jackson-modules-base#70]
// Copied from databind's test: com.fasterxml.jackson.databind.struct.ScalarCoercionTest
public class ScalarCoercionTest extends AfterburnerTestBase
{
private final ObjectMapper COERCING_MAPPER = newObjectMapper()
.enable(MapperFeature.ALLOW_COERCION_OF_SCALARS);

private final ObjectMapper NOT_COERCING_MAPPER = newObjectMapper()
.disable(MapperFeature.ALLOW_COERCION_OF_SCALARS);

/*
/**********************************************************
/* Unit tests: coercion from empty String
/**********************************************************
*/

public void testNullValueFromEmpty() throws Exception
{
// wrappers accept `null` fine
_verifyNullOkFromEmpty(Boolean.class, null);
// but primitives require non-null
_verifyNullOkFromEmpty(Boolean.TYPE, Boolean.FALSE);

_verifyNullOkFromEmpty(Byte.class, null);
_verifyNullOkFromEmpty(Byte.TYPE, Byte.valueOf((byte) 0));
_verifyNullOkFromEmpty(Short.class, null);
_verifyNullOkFromEmpty(Short.TYPE, Short.valueOf((short) 0));
_verifyNullOkFromEmpty(Character.class, null);
_verifyNullOkFromEmpty(Character.TYPE, Character.valueOf((char) 0));
_verifyNullOkFromEmpty(Integer.class, null);
_verifyNullOkFromEmpty(Integer.TYPE, Integer.valueOf(0));
_verifyNullOkFromEmpty(Long.class, null);
_verifyNullOkFromEmpty(Long.TYPE, Long.valueOf(0L));
_verifyNullOkFromEmpty(Float.class, null);
_verifyNullOkFromEmpty(Float.TYPE, Float.valueOf(0.0f));
_verifyNullOkFromEmpty(Double.class, null);
_verifyNullOkFromEmpty(Double.TYPE, Double.valueOf(0.0));

_verifyNullOkFromEmpty(BigInteger.class, null);
_verifyNullOkFromEmpty(BigDecimal.class, null);
}

private void _verifyNullOkFromEmpty(Class<?> type, Object exp) throws IOException
{
Object result = COERCING_MAPPER.readerFor(type)
.with(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)
.readValue("\"\"");
if (exp == null) {
assertNull(result);
} else {
assertEquals(exp, result);
}
}

public void testNullFailFromEmpty() throws Exception
{
_verifyNullFail(Boolean.class);
_verifyNullFail(Boolean.TYPE);

_verifyNullFail(Byte.class);
_verifyNullFail(Byte.TYPE);
_verifyNullFail(Short.class);
_verifyNullFail(Short.TYPE);
_verifyNullFail(Character.class);
_verifyNullFail(Character.TYPE);
_verifyNullFail(Integer.class);
_verifyNullFail(Integer.TYPE);
_verifyNullFail(Long.class);
_verifyNullFail(Long.TYPE);
_verifyNullFail(Float.class);
_verifyNullFail(Float.TYPE);
_verifyNullFail(Double.class);
_verifyNullFail(Double.TYPE);

_verifyNullFail(BigInteger.class);
_verifyNullFail(BigDecimal.class);
}

private void _verifyNullFail(Class<?> type) throws IOException
{
try {
NOT_COERCING_MAPPER.readerFor(type).readValue("\"\"");
fail("Should have failed for "+type);
} catch (MismatchedInputException e) {
verifyException(e, "Cannot coerce empty String");
verifyException(e, "Null value for");
}
}

/*
/**********************************************************
/* Unit tests: coercion from secondary representations
/**********************************************************
*/

public void testStringCoercionOk() throws Exception
{
// first successful coercions. Boolean has a ton...
_verifyCoerceSuccess("1", Boolean.TYPE, Boolean.TRUE);
_verifyCoerceSuccess("1", Boolean.class, Boolean.TRUE);
_verifyCoerceSuccess(quote("true"), Boolean.TYPE, Boolean.TRUE);
_verifyCoerceSuccess(quote("true"), Boolean.class, Boolean.TRUE);
_verifyCoerceSuccess(quote("True"), Boolean.TYPE, Boolean.TRUE);
_verifyCoerceSuccess(quote("True"), Boolean.class, Boolean.TRUE);
_verifyCoerceSuccess("0", Boolean.TYPE, Boolean.FALSE);
_verifyCoerceSuccess("0", Boolean.class, Boolean.FALSE);
_verifyCoerceSuccess(quote("false"), Boolean.TYPE, Boolean.FALSE);
_verifyCoerceSuccess(quote("false"), Boolean.class, Boolean.FALSE);
_verifyCoerceSuccess(quote("False"), Boolean.TYPE, Boolean.FALSE);
_verifyCoerceSuccess(quote("False"), Boolean.class, Boolean.FALSE);

_verifyCoerceSuccess(quote("123"), Byte.TYPE, Byte.valueOf((byte) 123));
_verifyCoerceSuccess(quote("123"), Byte.class, Byte.valueOf((byte) 123));
_verifyCoerceSuccess(quote("123"), Short.TYPE, Short.valueOf((short) 123));
_verifyCoerceSuccess(quote("123"), Short.class, Short.valueOf((short) 123));
_verifyCoerceSuccess(quote("123"), Integer.TYPE, Integer.valueOf(123));
_verifyCoerceSuccess(quote("123"), Integer.class, Integer.valueOf(123));
_verifyCoerceSuccess(quote("123"), Long.TYPE, Long.valueOf(123));
_verifyCoerceSuccess(quote("123"), Long.class, Long.valueOf(123));
_verifyCoerceSuccess(quote("123.5"), Float.TYPE, Float.valueOf(123.5f));
_verifyCoerceSuccess(quote("123.5"), Float.class, Float.valueOf(123.5f));
_verifyCoerceSuccess(quote("123.5"), Double.TYPE, Double.valueOf(123.5));
_verifyCoerceSuccess(quote("123.5"), Double.class, Double.valueOf(123.5));

_verifyCoerceSuccess(quote("123"), BigInteger.class, BigInteger.valueOf(123));
_verifyCoerceSuccess(quote("123.0"), BigDecimal.class, new BigDecimal("123.0"));
}

public void testStringCoercionFail() throws Exception
{
_verifyCoerceFail(quote("true"), Boolean.TYPE);
_verifyCoerceFail(quote("true"), Boolean.class);
_verifyCoerceFail(quote("123"), Byte.TYPE);
_verifyCoerceFail(quote("123"), Byte.class);
_verifyCoerceFail(quote("123"), Short.TYPE);
_verifyCoerceFail(quote("123"), Short.class);
_verifyCoerceFail(quote("123"), Integer.TYPE);
_verifyCoerceFail(quote("123"), Integer.class);
_verifyCoerceFail(quote("123"), Long.TYPE);
_verifyCoerceFail(quote("123"), Long.class);
_verifyCoerceFail(quote("123.5"), Float.TYPE);
_verifyCoerceFail(quote("123.5"), Float.class);
_verifyCoerceFail(quote("123.5"), Double.TYPE);
_verifyCoerceFail(quote("123.5"), Double.class);

_verifyCoerceFail(quote("123"), BigInteger.class);
_verifyCoerceFail(quote("123.0"), BigDecimal.class);
}

public void testMiscCoercionFail() throws Exception
{
// And then we have coercions from more esoteric types too
_verifyCoerceFail("1", Boolean.TYPE);
_verifyCoerceFail("1", Boolean.class);

_verifyCoerceFail("65", Character.class);
_verifyCoerceFail("65", Character.TYPE);
}

/*
/**********************************************************
/* Helper methods
/**********************************************************
*/

private void _verifyCoerceSuccess(String input, Class<?> type, Object exp) throws IOException
{
Object result = COERCING_MAPPER.readerFor(type)
.readValue(input);
assertEquals(exp, result);
}

private void _verifyCoerceFail(String input, Class<?> type) throws IOException
{
try {
NOT_COERCING_MAPPER.readerFor(type)
.readValue(input);
fail("Should not have allowed coercion");
} catch (MismatchedInputException e) {
verifyException(e, "Cannot coerce ");
verifyException(e, " for type `");
verifyException(e, "enable `MapperFeature.ALLOW_COERCION_OF_SCALARS` to allow");
}
}
}

0 comments on commit e315619

Please sign in to comment.