Skip to content

Commit

Permalink
CoercionConfig changes: int-to-boolean
Browse files Browse the repository at this point in the history
  • Loading branch information
cowtowncoder committed Jun 12, 2020
1 parent 1db376d commit 6b52007
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ protected final Boolean _parseBoolean(JsonParser p, DeserializationContext ctxt)
}
// should accept ints too, (0 == false, otherwise true)
if (t == JsonToken.VALUE_NUMBER_INT) {
return Boolean.valueOf(_parseBooleanFromInt(p, ctxt));
return _coerceBooleanFromInt(ctxt, p, Boolean.class);
}
// And finally, let's allow Strings to be converted too
if (t == JsonToken.VALUE_STRING) {
Expand Down Expand Up @@ -456,7 +456,11 @@ public Character deserialize(JsonParser p, DeserializationContext ctxt)
{
switch (p.currentTokenId()) {
case JsonTokenId.ID_NUMBER_INT: // ok iff Unicode value
_verifyNumberForScalarCoercion(ctxt, p);
// 12-Jun-2020, tatu: inlined from `StdDeserializer`
if (!ctxt.isEnabled(MapperFeature.ALLOW_COERCION_OF_SCALARS)) {
ctxt.reportInputMismatch(this,
"Cannot coerce Integer value to `Character` (enable `MapperFeature.ALLOW_COERCION_OF_SCALARS` to allow)");
}
int value = p.getIntValue();
if (value >= 0 && value <= 0xFFFF) {
return Character.valueOf((char) value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,9 @@ protected final boolean _parseBooleanPrimitive(DeserializationContext ctxt,

// should accept ints too, (0 == false, otherwise true)
if (t == JsonToken.VALUE_NUMBER_INT) {
return _parseBooleanFromInt(p, ctxt);
Boolean b = _coerceBooleanFromInt(ctxt, p, Boolean.TYPE);
// may get `null`, Boolean.TRUE or Boolean.FALSE so:
return (b == Boolean.TRUE);
}
// And finally, let's allow Strings to be converted too
if (t == JsonToken.VALUE_STRING) {
Expand Down Expand Up @@ -413,6 +415,7 @@ protected final boolean _parseBooleanPrimitive(DeserializationContext ctxt,
return ((Boolean) ctxt.handleUnexpectedToken(targetType, p)).booleanValue();
}

@Deprecated // since 2.12
protected boolean _parseBooleanFromInt(JsonParser p, DeserializationContext ctxt)
throws IOException
{
Expand Down Expand Up @@ -864,7 +867,7 @@ protected final static boolean _isBlank(String text)
* @since 2.12
*/
protected CoercionAction _checkFromStringCoercion(DeserializationContext ctxt, String value)
throws JsonMappingException
throws IOException
{
return _checkFromStringCoercion(ctxt, value, logicalType(), handledType());
}
Expand All @@ -874,7 +877,7 @@ protected CoercionAction _checkFromStringCoercion(DeserializationContext ctxt, S
*/
protected CoercionAction _checkFromStringCoercion(DeserializationContext ctxt, String value,
LogicalType logicalType, Class<?> rawTargetType)
throws JsonMappingException
throws IOException
{
final CoercionAction act;

Expand Down Expand Up @@ -904,6 +907,33 @@ protected CoercionAction _checkFromStringCoercion(DeserializationContext ctxt, S
return act;
}

/**
* @since 2.12
*/
protected Boolean _coerceBooleanFromInt(DeserializationContext ctxt, JsonParser p,
Class<?> rawTargetType)
throws IOException
{

final CoercionAction act = ctxt.findCoercionAction(LogicalType.Boolean, rawTargetType, CoercionInputShape.Integer);
switch (act) {
case Fail:
ctxt.reportInputMismatch(this,
"Cannot coerce Integer value (%s) to %s (but might if enabling coercion using `CoercionConfig`)",
p.getText(), _coercedTypeDesc());
case AsNull:
return null;
case AsEmpty:
return Boolean.FALSE;
default:
}
// 13-Oct-2016, tatu: As per [databind#1324], need to be careful wrt
// degenerate case of huge integers, legal in JSON.
// Also note that number tokens can not have WS to trim:
boolean b = !"0".equals(p.getText());
return b;
}

/*
/****************************************************
/* Helper methods for sub-classes, coercions, older (pre-2.12)
Expand Down Expand Up @@ -1055,7 +1085,7 @@ protected final void _verifyNullForScalarCoercion(DeserializationContext ctxt, S
}
}

// @since 2.9
@Deprecated // since 2.12
protected void _verifyNumberForScalarCoercion(DeserializationContext ctxt, JsonParser p) throws IOException
{
MapperFeature feat = MapperFeature.ALLOW_COERCION_OF_SCALARS;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,6 @@
// Tests for "old" coercions (pre-2.12), with `MapperFeature.ALLOW_COERCION_OF_SCALARS`
public class CoerceJDKScalarsTest extends BaseMapTest
{
static class BooleanPOJO {
public boolean value;
}

private final ObjectMapper COERCING_MAPPER = jsonMapperBuilder()
.enable(MapperFeature.ALLOW_COERCION_OF_SCALARS)
.build();
Expand Down Expand Up @@ -172,28 +168,16 @@ public void testStringCoercionFailFloat() throws Exception
_verifyRootStringCoerceFail("123.0", BigDecimal.class);
}

public void testToBooleanCoercionFailBytes() throws Exception
{
final String beanDoc = aposToQuotes("{'value':1}");
_verifyBooleanCoerceFail("1", true, JsonToken.VALUE_NUMBER_INT, "1", Boolean.TYPE);
_verifyBooleanCoerceFail("1", true, JsonToken.VALUE_NUMBER_INT, "1", Boolean.class);
_verifyBooleanCoerceFail(beanDoc, true, JsonToken.VALUE_NUMBER_INT, "1", BooleanPOJO.class);
}

public void testToBooleanCoercionFailChars() throws Exception
{
final String beanDoc = aposToQuotes("{'value':1}");
_verifyBooleanCoerceFail("1", false, JsonToken.VALUE_NUMBER_INT, "1", Boolean.TYPE);
_verifyBooleanCoerceFail("1", false, JsonToken.VALUE_NUMBER_INT, "1", Boolean.class);
_verifyBooleanCoerceFail(beanDoc, false, JsonToken.VALUE_NUMBER_INT, "1", BooleanPOJO.class);
}

public void testMiscCoercionFail() throws Exception
{
// And then we have coercions from more esoteric types too

_verifyCoerceFail("65", Character.class);
_verifyCoerceFail("65", Character.TYPE);
_verifyCoerceFail("65", Character.class,
"Cannot coerce Integer value to `Character",
"enable `MapperFeature.ALLOW_COERCION_OF_SCALARS` to allow");
_verifyCoerceFail("65", Character.TYPE,
"Cannot coerce Integer value to `Character",
"enable `MapperFeature.ALLOW_COERCION_OF_SCALARS` to allow");
}

/*
Expand All @@ -209,16 +193,15 @@ private void _verifyCoerceSuccess(String input, Class<?> type, Object exp) throw
assertEquals(exp, result);
}

private void _verifyCoerceFail(String input, Class<?> type) throws IOException
private void _verifyCoerceFail(String input, Class<?> type,
String... expMatches) 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, " to `");
verifyException(e, "enable `MapperFeature.ALLOW_COERCION_OF_SCALARS` to allow");
verifyException(e, expMatches);
}
}

Expand Down Expand Up @@ -256,44 +239,4 @@ private void _verifyStringCoerceFail(JsonParser p,
}
}

private void _verifyBooleanCoerceFail(String doc, boolean useBytes,
JsonToken tokenType, String tokenValue, Class<?> targetType) throws IOException
{
// Test failure for root value: for both byte- and char-backed sources.

// [databind#2635]: important, need to use `readValue()` that takes content and NOT
// JsonParser, as this forces closing of underlying parser and exposes more issues.

final ObjectReader r = NOT_COERCING_MAPPER.readerFor(targetType);
try {
if (useBytes) {
r.readValue(utf8Bytes(doc));
} else {
r.readValue(doc);
}
fail("Should not have allowed coercion");
} catch (MismatchedInputException e) {
_verifyBooleanCoerceFailReason(e, tokenType, tokenValue);
}
}

@SuppressWarnings("resource")
private void _verifyBooleanCoerceFailReason(MismatchedInputException e,
JsonToken tokenType, String tokenValue) throws IOException
{
verifyException(e, "Cannot coerce ");
verifyException(e, " to `");

JsonParser p = (JsonParser) e.getProcessor();

assertToken(tokenType, p.currentToken());

final String text = p.getText();

if (!tokenValue.equals(text)) {
String textDesc = (text == null) ? "NULL" : quote(text);
fail("Token text ("+textDesc+") via parser of type "+p.getClass().getName()
+" not as expected ("+quote(tokenValue)+")");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package com.fasterxml.jackson.databind.convert;

import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;

import com.fasterxml.jackson.databind.BaseMapTest;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.exc.MismatchedInputException;

public class CoerceToBooleanTest extends BaseMapTest
{
static class BooleanPOJO {
public boolean value;
}

private final ObjectMapper DEFAULT_MAPPER = sharedMapper();

private final ObjectMapper LEGACY_NONCOERCING_MAPPER = jsonMapperBuilder()
.disable(MapperFeature.ALLOW_COERCION_OF_SCALARS)
.build();

private final static String DOC_WITH_0 = aposToQuotes("{'value':0}");
private final static String DOC_WITH_1 = aposToQuotes("{'value':1}");

/*
/**********************************************************
/* Unit tests: default, legacy configuration
/**********************************************************
*/

public void testToBooleanCoercionSuccessPojo() throws Exception
{
BooleanPOJO p;
final ObjectReader r = DEFAULT_MAPPER.readerFor(BooleanPOJO.class);

p = r.readValue(DOC_WITH_0);
assertEquals(false, p.value);
p = r.readValue(utf8Bytes(DOC_WITH_0));
assertEquals(false, p.value);

p = r.readValue(DOC_WITH_1);
assertEquals(true, p.value);
p = r.readValue(utf8Bytes(DOC_WITH_1));
assertEquals(true, p.value);
}

public void testToBooleanCoercionSuccessRoot() throws Exception
{
final ObjectReader br = DEFAULT_MAPPER.readerFor(Boolean.class);

assertEquals(Boolean.FALSE, br.readValue(" 0"));
assertEquals(Boolean.FALSE, br.readValue(utf8Bytes(" 0")));
assertEquals(Boolean.TRUE, br.readValue(" -1"));
assertEquals(Boolean.TRUE, br.readValue(utf8Bytes(" -1")));

final ObjectReader atomicR = DEFAULT_MAPPER.readerFor(AtomicBoolean.class);

AtomicBoolean ab;

ab = atomicR.readValue(" 0");
ab = atomicR.readValue(utf8Bytes(" 0"));
assertEquals(false, ab.get());

ab = atomicR.readValue(" 111");
assertEquals(true, ab.get());
ab = atomicR.readValue(utf8Bytes(" 111"));
assertEquals(true, ab.get());
}

public void testToBooleanCoercionFailBytes() throws Exception
{
_verifyBooleanCoerceFail(aposToQuotes("{'value':1}"), true, JsonToken.VALUE_NUMBER_INT, "1", BooleanPOJO.class);

_verifyBooleanCoerceFail("1", true, JsonToken.VALUE_NUMBER_INT, "1", Boolean.TYPE);
_verifyBooleanCoerceFail("1", true, JsonToken.VALUE_NUMBER_INT, "1", Boolean.class);
}

public void testToBooleanCoercionFailChars() throws Exception
{
_verifyBooleanCoerceFail(aposToQuotes("{'value':1}"), false, JsonToken.VALUE_NUMBER_INT, "1", BooleanPOJO.class);

_verifyBooleanCoerceFail("1", false, JsonToken.VALUE_NUMBER_INT, "1", Boolean.TYPE);
_verifyBooleanCoerceFail("1", false, JsonToken.VALUE_NUMBER_INT, "1", Boolean.class);
}

/*
/**********************************************************
/* Unit tests: new CoercionConfig
/**********************************************************
*/

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

private void _verifyBooleanCoerceFail(String doc, boolean useBytes,
JsonToken tokenType, String tokenValue, Class<?> targetType) throws IOException
{
// Test failure for root value: for both byte- and char-backed sources.

// [databind#2635]: important, need to use `readValue()` that takes content and NOT
// JsonParser, as this forces closing of underlying parser and exposes more issues.

final ObjectReader r = LEGACY_NONCOERCING_MAPPER.readerFor(targetType);
try {
if (useBytes) {
r.readValue(utf8Bytes(doc));
} else {
r.readValue(doc);
}
fail("Should not have allowed coercion");
} catch (MismatchedInputException e) {
_verifyBooleanCoerceFailReason(e, tokenType, tokenValue);
}
}

@SuppressWarnings("resource")
private void _verifyBooleanCoerceFailReason(MismatchedInputException e,
JsonToken tokenType, String tokenValue) throws IOException
{
verifyException(e, "Cannot coerce ");
verifyException(e, " to `");

JsonParser p = (JsonParser) e.getProcessor();

assertToken(tokenType, p.currentToken());

final String text = p.getText();
if (!tokenValue.equals(text)) {
String textDesc = (text == null) ? "NULL" : quote(text);
fail("Token text ("+textDesc+") via parser of type "+p.getClass().getName()
+" not as expected ("+quote(tokenValue)+")");
}
}
}

0 comments on commit 6b52007

Please sign in to comment.