Skip to content

Commit

Permalink
Fix FasterXML#3503: Implement int-to-float coercion config
Browse files Browse the repository at this point in the history
  • Loading branch information
Tomasito665 committed Jun 30, 2022
1 parent 480bc60 commit d4ad183
Show file tree
Hide file tree
Showing 4 changed files with 238 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -611,8 +611,16 @@ protected final Float _parseFloat(JsonParser p, DeserializationContext ctxt)
break;
case JsonTokenId.ID_NULL: // null fine for non-primitive
return (Float) getNullValue(ctxt);
case JsonTokenId.ID_NUMBER_INT:
final CoercionAction act = _checkIntToFloatCoercion(p, ctxt, _valueClass);
if (act == CoercionAction.AsNull) {
return (Float) getNullValue(ctxt);
}
if (act == CoercionAction.AsEmpty) {
return (Float) getEmptyValue(ctxt);
}
// fall through to coerce
case JsonTokenId.ID_NUMBER_FLOAT:
case JsonTokenId.ID_NUMBER_INT: // safe coercion
return p.getFloatValue();
// 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
case JsonTokenId.ID_START_OBJECT:
Expand Down Expand Up @@ -700,8 +708,16 @@ protected final Double _parseDouble(JsonParser p, DeserializationContext ctxt) t
break;
case JsonTokenId.ID_NULL: // null fine for non-primitive
return (Double) getNullValue(ctxt);
case JsonTokenId.ID_NUMBER_FLOAT:
case JsonTokenId.ID_NUMBER_INT: // safe coercion
case JsonTokenId.ID_NUMBER_INT:
final CoercionAction act = _checkIntToFloatCoercion(p, ctxt, _valueClass);
if (act == CoercionAction.AsNull) {
return (Double) getNullValue(ctxt);
}
if (act == CoercionAction.AsEmpty) {
return (Double) getEmptyValue(ctxt);
}
// fall through to coerce
case JsonTokenId.ID_NUMBER_FLOAT: // safe coercion
return p.getDoubleValue();
// 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
case JsonTokenId.ID_START_OBJECT:
Expand Down Expand Up @@ -977,6 +993,14 @@ public BigDecimal deserialize(JsonParser p, DeserializationContext ctxt)
String text;
switch (p.currentTokenId()) {
case JsonTokenId.ID_NUMBER_INT:
final CoercionAction act = _checkIntToFloatCoercion(p, ctxt, _valueClass);
if (act == CoercionAction.AsNull) {
return (BigDecimal) getNullValue(ctxt);
}
if (act == CoercionAction.AsEmpty) {
return (BigDecimal) getEmptyValue(ctxt);
}
// fall through to coerce
case JsonTokenId.ID_NUMBER_FLOAT:
return p.getDecimalValue();
case JsonTokenId.ID_STRING:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -984,6 +984,14 @@ protected final float _parseFloatPrimitive(JsonParser p, DeserializationContext
text = p.getText();
break;
case JsonTokenId.ID_NUMBER_INT:
final CoercionAction act = _checkIntToFloatCoercion(p, ctxt, Float.TYPE);
if (act == CoercionAction.AsNull) {
return 0.0f;
}
if (act == CoercionAction.AsEmpty) {
return 0.0f;
}
// fall through to coerce
case JsonTokenId.ID_NUMBER_FLOAT:
return p.getFloatValue();
case JsonTokenId.ID_NULL:
Expand Down Expand Up @@ -1105,6 +1113,14 @@ protected final double _parseDoublePrimitive(JsonParser p, DeserializationContex
text = p.getText();
break;
case JsonTokenId.ID_NUMBER_INT:
final CoercionAction act = _checkIntToFloatCoercion(p, ctxt, Double.TYPE);
if (act == CoercionAction.AsNull) {
return 0.0d;
}
if (act == CoercionAction.AsEmpty) {
return 0.0d;
}
// fall through to coerce
case JsonTokenId.ID_NUMBER_FLOAT:
return p.getDoubleValue();
case JsonTokenId.ID_NULL:
Expand Down Expand Up @@ -1474,6 +1490,22 @@ protected CoercionAction _checkFloatToIntCoercion(JsonParser p, DeserializationC
return act;
}

/**
* @since 2.14
*/
protected CoercionAction _checkIntToFloatCoercion(JsonParser p, DeserializationContext ctxt,
Class<?> rawTargetType)
throws IOException
{
final CoercionAction act = ctxt.findCoercionAction(LogicalType.Float,
rawTargetType, CoercionInputShape.Integer);
if (act == CoercionAction.Fail) {
return _checkCoercionFail(ctxt, act, rawTargetType, p.getNumberValue(),
"Integer value (" + p.getText() + ")");
}
return act;
}

/**
* @since 2.12
*/
Expand Down
7 changes: 7 additions & 0 deletions src/test/java/com/fasterxml/jackson/databind/BaseMapTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ public LongWrapper() { }
public LongWrapper(long value) { l = value; }
}

protected static class FloatWrapper {
public float f;

public FloatWrapper() { }
public FloatWrapper(float value) { f = value; }
}

protected static class DoubleWrapper {
public double d;

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

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.BaseMapTest;
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;
import java.math.BigDecimal;

public class CoerceIntToFloatTest extends BaseMapTest
{
private final ObjectMapper DEFAULT_MAPPER = newJsonMapper();

private final ObjectMapper MAPPER_TO_FAIL = jsonMapperBuilder()
.withCoercionConfig(LogicalType.Float, cfg ->
cfg.setCoercion(CoercionInputShape.Integer, CoercionAction.Fail))
.build();

private final ObjectMapper MAPPER_TRY_CONVERT = jsonMapperBuilder()
.withCoercionConfig(LogicalType.Float, cfg ->
cfg.setCoercion(CoercionInputShape.Integer, CoercionAction.TryConvert))
.build();

private final ObjectMapper MAPPER_TO_NULL = jsonMapperBuilder()
.withCoercionConfig(LogicalType.Float, cfg ->
cfg.setCoercion(CoercionInputShape.Integer, CoercionAction.AsNull))
.build();

private final ObjectMapper MAPPER_TO_EMPTY = jsonMapperBuilder()
.withCoercionConfig(LogicalType.Float, cfg ->
cfg.setCoercion(CoercionInputShape.Integer, CoercionAction.AsEmpty))
.build();

public void testDefaultIntToFloatCoercion() throws JsonProcessingException
{
assertSuccessfulIntToFloatConversionsWith(DEFAULT_MAPPER);
}

public void testCoerceConfigToConvert() throws JsonProcessingException
{
assertSuccessfulIntToFloatConversionsWith(MAPPER_TRY_CONVERT);
}

public void testCoerceConfigToNull() throws JsonProcessingException
{
assertNull(MAPPER_TO_NULL.readValue("1", Float.class));
// `null` not possible for primitives, must use empty (aka default) value
assertEquals(0.0f, MAPPER_TO_NULL.readValue("-2", Float.TYPE));
{
FloatWrapper w = MAPPER_TO_NULL.readValue("{\"f\": -5}", FloatWrapper.class);
assertEquals(0.0f, w.f);
float[] arr = MAPPER_TO_NULL.readValue("[ 2 ]", float[].class);
assertEquals(1, arr.length);
assertEquals(0.0f, arr[0]);
}

assertNull(MAPPER_TO_NULL.readValue("-1", Double.class));
assertEquals(0.0d, MAPPER_TO_NULL.readValue("4", Double.TYPE));
{
DoubleWrapper w = MAPPER_TO_NULL.readValue("{\"d\": 2}", DoubleWrapper.class);
assertEquals(0.0d, w.d);
double[] arr = MAPPER_TO_NULL.readValue("[ -7 ]", double[].class);
assertEquals(1, arr.length);
assertEquals(0.0d, arr[0]);
}

assertNull(MAPPER_TO_NULL.readValue("420", BigDecimal.class));
{
BigDecimal[] arr = MAPPER_TO_NULL.readValue("[ 420 ]", BigDecimal[].class);
assertEquals(1, arr.length);
assertNull(arr[0]);
}
}

public void testCoerceConfigToEmpty() throws JsonProcessingException
{
assertEquals(0.0f, MAPPER_TO_EMPTY.readValue("3", Float.class));
assertEquals(0.0f, MAPPER_TO_EMPTY.readValue("-2", Float.TYPE));
{
FloatWrapper w = MAPPER_TO_EMPTY.readValue("{\"f\": -5}", FloatWrapper.class);
assertEquals(0.0f, w.f);
float[] arr = MAPPER_TO_EMPTY.readValue("[ 2 ]", float[].class);
assertEquals(1, arr.length);
assertEquals(0.0f, arr[0]);
}

assertEquals(0.0d, MAPPER_TO_EMPTY.readValue("-1", Double.class));
assertEquals(0.0d, MAPPER_TO_EMPTY.readValue("-5", Double.TYPE));
{
DoubleWrapper w = MAPPER_TO_EMPTY.readValue("{\"d\": 2}", DoubleWrapper.class);
assertEquals(0.0d, w.d);
double[] arr = MAPPER_TO_EMPTY.readValue("[ -2 ]", double[].class);
assertEquals(1, arr.length);
assertEquals(0.0d, arr[0]);
}

assertEquals(BigDecimal.valueOf(0), MAPPER_TO_EMPTY.readValue("3643", BigDecimal.class));
}

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, 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, BigDecimal.class, "73455342");
}

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

private void assertSuccessfulIntToFloatConversionsWith(ObjectMapper objectMapper)
throws JsonProcessingException
{
assertEquals(3.0f, objectMapper.readValue("3", Float.class));
assertEquals(-2.0f, objectMapper.readValue("-2", Float.TYPE));
{
FloatWrapper w = objectMapper.readValue("{\"f\": -5}", FloatWrapper.class);
assertEquals(-5.0f, w.f);
float[] arr = objectMapper.readValue("[ 2 ]", float[].class);
assertEquals(2.0f, arr[0]);
}

assertEquals(-1.0d, objectMapper.readValue("-1", Double.class));
assertEquals(4.0d, objectMapper.readValue("4", Double.TYPE));
{
DoubleWrapper w = objectMapper.readValue("{\"d\": 2}", DoubleWrapper.class);
assertEquals(2.0d, w.d);
double[] arr = objectMapper.readValue("[ -2 ]", double[].class);
assertEquals(-2.0d, arr[0]);
}

BigDecimal biggie = objectMapper.readValue("423451233", BigDecimal.class);
assertEquals(BigDecimal.valueOf(423451233.0d), biggie);
}

private void _verifyCoerceFail(ObjectMapper m, Class<?> targetType,
String doc) throws JsonProcessingException
{
_verifyCoerceFail(m.reader(), targetType, doc, targetType.getName());
}

private void _verifyCoerceFail(ObjectMapper m, Class<?> targetType,
String doc, String targetTypeDesc) throws JsonProcessingException
{
_verifyCoerceFail(m.reader(), targetType, doc, targetTypeDesc);
}

private void _verifyCoerceFail(ObjectReader r, Class<?> targetType,
String doc, String targetTypeDesc) throws JsonProcessingException
{
try {
r.forType(targetType).readValue(doc);
fail("Should not accept Integer for "+targetType.getName()+" when configured to");
} catch (MismatchedInputException e) {
verifyException(e, "Cannot coerce Integer");
verifyException(e, targetTypeDesc);
}
}
}

0 comments on commit d4ad183

Please sign in to comment.