Skip to content

Commit

Permalink
Complete int->boolean coercion handling changes, tests
Browse files Browse the repository at this point in the history
  • Loading branch information
cowtowncoder committed Jun 12, 2020
1 parent 6b52007 commit a25fbce
Show file tree
Hide file tree
Showing 4 changed files with 204 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.util.concurrent.atomic.AtomicBoolean;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.type.LogicalType;

Expand All @@ -15,7 +16,17 @@ public class AtomicBooleanDeserializer extends StdScalarDeserializer<AtomicBoole

@Override
public AtomicBoolean deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return new AtomicBoolean(_parseBooleanPrimitive(ctxt, p, AtomicBoolean.class));
JsonToken t = p.currentToken();
if (t == JsonToken.VALUE_TRUE) {
return new AtomicBoolean(true);
}
if (t == JsonToken.VALUE_FALSE) {
return new AtomicBoolean(false);
}
// 12-Jun-2020, tatu: May look convoluted, but need to work correctly with
// CoercionConfig
Boolean b = _parseBoolean(ctxt, p, AtomicBoolean.class);
return (b == null) ? null : new AtomicBoolean(b.booleanValue());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,10 @@ public Boolean deserialize(JsonParser p, DeserializationContext ctxt) throws IOE
if (t == JsonToken.VALUE_FALSE) {
return Boolean.FALSE;
}
return _parseBoolean(p, ctxt);
if (_primitive) {
return _parseBooleanPrimitive(ctxt, p, _valueClass);
}
return _parseBoolean(ctxt, p, _valueClass);
}

// Since we can never have type info ("natural type"; String, Boolean, Integer, Double):
Expand All @@ -236,56 +239,10 @@ public Boolean deserializeWithType(JsonParser p, DeserializationContext ctxt,
if (t == JsonToken.VALUE_FALSE) {
return Boolean.FALSE;
}
return _parseBoolean(p, ctxt);
}

protected final Boolean _parseBoolean(JsonParser p, DeserializationContext ctxt)
throws IOException
{
JsonToken t = p.currentToken();
if (t == JsonToken.VALUE_NULL) {
return (Boolean) _coerceNullToken(ctxt, _primitive);
}
if (t == JsonToken.START_ARRAY) { // unwrapping?
return _deserializeFromArray(p, ctxt);
}
// should accept ints too, (0 == false, otherwise true)
if (t == JsonToken.VALUE_NUMBER_INT) {
return _coerceBooleanFromInt(ctxt, p, Boolean.class);
}
// And finally, let's allow Strings to be converted too
if (t == JsonToken.VALUE_STRING) {
String text = p.getText();
CoercionAction act = _checkFromStringCoercion(ctxt, text);
if (act == CoercionAction.AsNull) {
return (Boolean) getNullValue(ctxt);
}
if (act == CoercionAction.AsEmpty) {
return (Boolean) getEmptyValue(ctxt);
}
text = text.trim();
// [databind#422]: Allow aliases
if ("true".equals(text) || "True".equals(text)) {
return Boolean.TRUE;
}
if ("false".equals(text) || "False".equals(text)) {
return Boolean.FALSE;
}
if (_hasTextualNull(text)) {
return (Boolean) _coerceTextualNull(ctxt, _primitive);
}
return (Boolean) ctxt.handleWeirdStringValue(_valueClass, text,
"only \"true\" or \"false\" recognized");
}
// usually caller should have handled but:
if (t == JsonToken.VALUE_TRUE) {
return Boolean.TRUE;
}
if (t == JsonToken.VALUE_FALSE) {
return Boolean.FALSE;
if (_primitive) {
return _parseBooleanPrimitive(ctxt, p, _valueClass);
}
// Otherwise, no can do:
return (Boolean) ctxt.handleUnexpectedToken(_valueClass, p);
return _parseBoolean(ctxt, p, _valueClass);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ protected final boolean _parseBooleanPrimitive(DeserializationContext ctxt,
return false;
}

// should accept ints too, (0 == false, otherwise true)
// may accept ints too, (0 == false, otherwise true)
if (t == JsonToken.VALUE_NUMBER_INT) {
Boolean b = _coerceBooleanFromInt(ctxt, p, Boolean.TYPE);
// may get `null`, Boolean.TRUE or Boolean.FALSE so:
Expand Down Expand Up @@ -415,6 +415,57 @@ protected final boolean _parseBooleanPrimitive(DeserializationContext ctxt,
return ((Boolean) ctxt.handleUnexpectedToken(targetType, p)).booleanValue();
}

protected final Boolean _parseBoolean(DeserializationContext ctxt,
JsonParser p, Class<?> targetType)
throws IOException
{
JsonToken t = p.currentToken();
if (t == JsonToken.VALUE_NULL) {
return (Boolean) _coerceNullToken(ctxt, false);
}
// may accept ints too, (0 == false, otherwise true)
if (t == JsonToken.VALUE_NUMBER_INT) {
return _coerceBooleanFromInt(ctxt, p, Boolean.class);
}
// And finally, let's allow Strings to be converted too
if (t == JsonToken.VALUE_STRING) {
String text = p.getText();
CoercionAction act = _checkFromStringCoercion(ctxt, text,
LogicalType.Boolean, targetType);
if (act == CoercionAction.AsNull) {
return (Boolean) getNullValue(ctxt);
}
if (act == CoercionAction.AsEmpty) {
return (Boolean) getEmptyValue(ctxt);
}
text = text.trim();
// [databind#422]: Allow aliases
if ("true".equals(text) || "True".equals(text)) {
return Boolean.TRUE;
}
if ("false".equals(text) || "False".equals(text)) {
return Boolean.FALSE;
}
if (_hasTextualNull(text)) {
return (Boolean) _coerceTextualNull(ctxt, false);
}
return (Boolean) ctxt.handleWeirdStringValue(_valueClass, text,
"only \"true\" or \"false\" recognized");
}
// usually caller should have handled but:
if (t == JsonToken.VALUE_TRUE) {
return Boolean.TRUE;
}
if (t == JsonToken.VALUE_FALSE) {
return Boolean.FALSE;
}
if (t == JsonToken.START_ARRAY) { // unwrapping?
return (Boolean) _deserializeFromArray(p, ctxt);
}
// Otherwise, no can do:
return (Boolean) ctxt.handleUnexpectedToken(targetType, p);
}

@Deprecated // since 2.12
protected boolean _parseBooleanFromInt(JsonParser p, DeserializationContext ctxt)
throws IOException
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.cfg.CoercionAction;
import com.fasterxml.jackson.databind.cfg.CoercionInputShape;
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
import com.fasterxml.jackson.databind.type.LogicalType;

public class CoerceToBooleanTest extends BaseMapTest
{
Expand All @@ -24,9 +27,33 @@ static class BooleanPOJO {
.disable(MapperFeature.ALLOW_COERCION_OF_SCALARS)
.build();

private final ObjectMapper MAPPER_TO_EMPTY; {
MAPPER_TO_EMPTY = newJsonMapper();
MAPPER_TO_EMPTY.coercionConfigFor(LogicalType.Boolean)
.setCoercion(CoercionInputShape.Integer, CoercionAction.AsEmpty);
}

private final ObjectMapper MAPPER_TRY_CONVERT; {
MAPPER_TRY_CONVERT = newJsonMapper();
MAPPER_TRY_CONVERT.coercionConfigFor(LogicalType.Boolean)
.setCoercion(CoercionInputShape.Integer, CoercionAction.TryConvert);
}

private final ObjectMapper MAPPER_TO_NULL; {
MAPPER_TO_NULL = newJsonMapper();
MAPPER_TO_NULL.coercionConfigFor(LogicalType.Boolean)
.setCoercion(CoercionInputShape.Integer, CoercionAction.AsNull);
}

private final ObjectMapper MAPPER_TO_FAIL; {
MAPPER_TO_FAIL = newJsonMapper();
MAPPER_TO_FAIL.coercionConfigFor(LogicalType.Boolean)
.setCoercion(CoercionInputShape.Integer, CoercionAction.Fail);
}

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
Expand Down Expand Up @@ -90,10 +117,98 @@ public void testToBooleanCoercionFailChars() throws Exception

/*
/**********************************************************
/* Unit tests: new CoercionConfig
/* Unit tests: new CoercionConfig, as-null, as-empty, try-coerce
/**********************************************************
*/

public void testIntToNullCoercion() throws Exception
{
assertNull(MAPPER_TO_NULL.readValue("0", Boolean.class));
assertNull(MAPPER_TO_NULL.readValue("1", Boolean.class));

// but due to coercion to `boolean`, can not return null here -- however,
// goes "1 -> false (no null for primitive) -> Boolean.FALSE
assertEquals(Boolean.FALSE, MAPPER_TO_NULL.readValue("0", Boolean.TYPE));
assertEquals(Boolean.FALSE, MAPPER_TO_NULL.readValue("1", Boolean.TYPE));

// As to AtomicBoolean: that type itself IS nullable since it's of LogicalType.Boolean so
assertNull(MAPPER_TO_NULL.readValue("0", AtomicBoolean.class));
assertNull(MAPPER_TO_NULL.readValue("1", AtomicBoolean.class));

BooleanPOJO p;
p = MAPPER_TO_NULL.readValue(DOC_WITH_0, BooleanPOJO.class);
assertFalse(p.value);
p = MAPPER_TO_NULL.readValue(DOC_WITH_1, BooleanPOJO.class);
assertFalse(p.value);
}

public void testIntToEmptyCoercion() throws Exception
{
// "empty" value for Boolean/boolean is False/false

assertEquals(Boolean.FALSE, MAPPER_TO_EMPTY.readValue("0", Boolean.class));
assertEquals(Boolean.FALSE, MAPPER_TO_EMPTY.readValue("1", Boolean.class));

assertEquals(Boolean.FALSE, MAPPER_TO_EMPTY.readValue("0", Boolean.TYPE));
assertEquals(Boolean.FALSE, MAPPER_TO_EMPTY.readValue("1", Boolean.TYPE));

AtomicBoolean ab;
ab = MAPPER_TO_EMPTY.readValue("0", AtomicBoolean.class);
assertFalse(ab.get());
ab = MAPPER_TO_EMPTY.readValue("1", AtomicBoolean.class);
assertFalse(ab.get());

BooleanPOJO p;
p = MAPPER_TO_EMPTY.readValue(DOC_WITH_0, BooleanPOJO.class);
assertFalse(p.value);
p = MAPPER_TO_EMPTY.readValue(DOC_WITH_1, BooleanPOJO.class);
assertFalse(p.value);
}

public void testIntToTryCoercion() throws Exception
{
// And "TryCoerce" should do what would be typically expected

assertEquals(Boolean.FALSE, MAPPER_TRY_CONVERT.readValue("0", Boolean.class));
assertEquals(Boolean.TRUE, MAPPER_TRY_CONVERT.readValue("1", Boolean.class));

assertEquals(Boolean.FALSE, MAPPER_TRY_CONVERT.readValue("0", Boolean.TYPE));
assertEquals(Boolean.TRUE, MAPPER_TRY_CONVERT.readValue("1", Boolean.TYPE));

AtomicBoolean ab;
ab = MAPPER_TRY_CONVERT.readValue("0", AtomicBoolean.class);
assertFalse(ab.get());
ab = MAPPER_TRY_CONVERT.readValue("1", AtomicBoolean.class);
assertTrue(ab.get());

BooleanPOJO p;
p = MAPPER_TRY_CONVERT.readValue(DOC_WITH_0, BooleanPOJO.class);
assertFalse(p.value);
p = MAPPER_TRY_CONVERT.readValue(DOC_WITH_1, BooleanPOJO.class);
assertTrue(p.value);
}

/*
/**********************************************************
/* Unit tests: new CoercionConfig, failing
/**********************************************************
*/

public void testFailFromInteger() throws Exception
{
_verifyFailFromInteger(MAPPER_TO_FAIL, BooleanPOJO.class, DOC_WITH_0, Boolean.TYPE);
_verifyFailFromInteger(MAPPER_TO_FAIL, BooleanPOJO.class, DOC_WITH_1, Boolean.TYPE);

_verifyFailFromInteger(MAPPER_TO_FAIL, Boolean.class, "0");
_verifyFailFromInteger(MAPPER_TO_FAIL, Boolean.class, "42");

_verifyFailFromInteger(MAPPER_TO_FAIL, Boolean.TYPE, "0");
_verifyFailFromInteger(MAPPER_TO_FAIL, Boolean.TYPE, "999");

_verifyFailFromInteger(MAPPER_TO_FAIL, AtomicBoolean.class, "0");
_verifyFailFromInteger(MAPPER_TO_FAIL, AtomicBoolean.class, "-123");
}

/*
/**********************************************************
/* Helper methods
Expand Down Expand Up @@ -139,4 +254,20 @@ private void _verifyBooleanCoerceFailReason(MismatchedInputException e,
+" not as expected ("+quote(tokenValue)+")");
}
}

private void _verifyFailFromInteger(ObjectMapper m, Class<?> targetType, String doc) throws Exception {
_verifyFailFromInteger(m, targetType, doc, targetType);
}

private void _verifyFailFromInteger(ObjectMapper m, Class<?> targetType, String doc,
Class<?> valueType) throws Exception
{
try {
m.readerFor(targetType).readValue(doc);
fail("Should not accept Integer for "+targetType.getName()+" by default");
} catch (MismatchedInputException e) {
verifyException(e, "Cannot coerce Integer value");
verifyException(e, "to `"+valueType.getName()+"`");
}
}
}

0 comments on commit a25fbce

Please sign in to comment.