Skip to content

Commit

Permalink
Add DeserializationFeature.FAIL_ON_NAN_TO_BIG_DECIMAL_COERCION to d…
Browse files Browse the repository at this point in the history
…etermine what happens on `JsonNode` coercion to `BigDecimal` with NaN (#4195)
  • Loading branch information
JooHyukKim authored Nov 29, 2023
1 parent 5612aba commit 722edd4
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,25 @@ public enum JsonNodeFeature implements DatatypeFeature
*
* @since 2.15
*/
STRIP_TRAILING_BIGDECIMAL_ZEROES(true)
STRIP_TRAILING_BIGDECIMAL_ZEROES(true),

/**
* Determines the behavior when coercing `NaN` to {@link java.math.BigDecimal} with
* {@link com.fasterxml.jackson.databind.DeserializationFeature#USE_BIG_DECIMAL_FOR_FLOATS} enabled.
*
* 1. If set to {@code true}, will throw an {@link com.fasterxml.jackson.databind.exc.InvalidFormatException} for
* attempting to coerce {@code NaN} into {@link java.math.BigDecimal}.
* 2. If set to {@code false}, will simply let coercing {@code NaN} into {@link java.math.BigDecimal} happen,
* regardless of how such coercion will behave --as of 2.16, will simply stay as {@code NaN} of original
* floating-point type node.
*
* <p>
* Default value is {@code false} for backwards-compatibility, but will most likely be changed to
* {@code true} in 3.0.
*
* @since 2.16
*/
FAIL_ON_NAN_TO_BIG_DECIMAL_COERCION(false)
;

private final static int FEATURE_INDEX = DatatypeFeatures.FEATURE_INDEX_JSON_NODE;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,12 @@ protected final JsonNode _fromFloat(JsonParser p, DeserializationContext ctxt,
return _fromBigDecimal(ctxt, nodeFactory, p.getDecimalValue());
}
if (ctxt.isEnabled(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)) {
// [databind#4194] Add an option to fail coercing NaN to BigDecimal
// Currently, Jackson 2.x allows such coercion, but Jackson 3.x will not
if (p.isNaN() && ctxt.isEnabled(JsonNodeFeature.FAIL_ON_NAN_TO_BIG_DECIMAL_COERCION)) {
ctxt.handleWeirdNumberValue(handledType(), p.getDoubleValue(),
"Cannot convert NaN into BigDecimal");
}
try {
return _fromBigDecimal(ctxt, nodeFactory, p.getDecimalValue());
} catch (NumberFormatException nfe) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.fasterxml.jackson.databind.node;

import com.fasterxml.jackson.databind.cfg.JsonNodeFeature;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import java.math.BigDecimal;

import com.fasterxml.jackson.core.JsonFactory;
Expand Down Expand Up @@ -39,4 +41,36 @@ public void testBigDecimalCoercionInf() throws Exception
assertTrue("Expected DoubleNode, got: "+jsonNode.getClass().getName()+": "+jsonNode, jsonNode.isDouble());
assertEquals(Double.POSITIVE_INFINITY, jsonNode.doubleValue());
}

// [databind#4194]: should be able to, by configuration, fail coercing NaN to BigDecimal
public void testBigDecimalCoercionNaN() throws Exception
{
_tryBigDecimalCoercionNaNWithOption(false);

try {
_tryBigDecimalCoercionNaNWithOption(true);
fail("Should not pass");
} catch (InvalidFormatException e) {
verifyException(e, "Cannot convert NaN");
}
}

private void _tryBigDecimalCoercionNaNWithOption(boolean isEnabled) throws Exception
{
JsonFactory factory = JsonFactory.builder()
.enable(JsonReadFeature.ALLOW_NON_NUMERIC_NUMBERS)
.build();
final ObjectReader reader = new JsonMapper(factory)
.reader()
.with(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);

final String value = "NaN";
// depending on option
final JsonNode jsonNode = isEnabled
? reader.with(JsonNodeFeature.FAIL_ON_NAN_TO_BIG_DECIMAL_COERCION).readTree(value)
: reader.without(JsonNodeFeature.FAIL_ON_NAN_TO_BIG_DECIMAL_COERCION).readTree(value);

assertTrue("Expected DoubleNode, got: "+jsonNode.getClass().getName()+": "+jsonNode, jsonNode.isDouble());
assertEquals(Double.NaN, jsonNode.doubleValue());
}
}

0 comments on commit 722edd4

Please sign in to comment.