Skip to content

Commit 6202398

Browse files
authored
Add new JsonNodeFeature.USE_BIG_DECIMAL_FOR_FLOATS (#5058)
1 parent 9c51b81 commit 6202398

File tree

3 files changed

+87
-2
lines changed

3 files changed

+87
-2
lines changed

src/main/java/com/fasterxml/jackson/databind/cfg/JsonNodeFeature.java

+24-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,30 @@ public enum JsonNodeFeature implements DatatypeFeature
7979
*
8080
* @since 2.16
8181
*/
82-
FAIL_ON_NAN_TO_BIG_DECIMAL_COERCION(false)
82+
FAIL_ON_NAN_TO_BIG_DECIMAL_COERCION(false),
83+
84+
/**
85+
* Determines whether floating-point numbers should be deserialized into
86+
* {@link java.math.BigDecimal} when reading {@link com.fasterxml.jackson.databind.JsonNode}s.
87+
* This feature provides more precise control over number deserialization for {@code JsonNode}
88+
* and takes precedence over {@link com.fasterxml.jackson.databind.DeserializationFeature#USE_BIG_DECIMAL_FOR_FLOATS}
89+
* if explicitly set.
90+
*
91+
* <p>
92+
* Behavior follows these rules:
93+
* <ul>
94+
* <li>If explicitly enabled, floating-point numbers will be read as {@link java.math.BigDecimal}.</li>
95+
* <li>If explicitly disabled, floating-point numbers will be read as {@link java.lang.Double}.</li>
96+
* <li>If left undefined (default), the behavior follows {@code DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS}.</li>
97+
* </ul>
98+
*
99+
* <p>
100+
* Default value is {@code false} but unless explicitly set, handling
101+
* depends on more general {@code DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS}).
102+
*
103+
* @since 2.19
104+
*/
105+
USE_BIG_DECIMAL_FOR_FLOATS(false),
83106
;
84107

85108
private final static int FEATURE_INDEX = DatatypeFeatures.FEATURE_INDEX_JSON_NODE;

src/main/java/com/fasterxml/jackson/databind/deser/std/JsonNodeDeserializer.java

+9-1
Original file line numberDiff line numberDiff line change
@@ -765,7 +765,15 @@ protected final JsonNode _fromFloat(JsonParser p, DeserializationContext ctxt,
765765
if (nt == JsonParser.NumberTypeFP.BIG_DECIMAL) {
766766
return _fromBigDecimal(ctxt, nodeFactory, p.getDecimalValue());
767767
}
768-
if (ctxt.isEnabled(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)) {
768+
769+
// [databind#4801] Add JsonNodeFeature.USE_BIG_DECIMAL_FOR_FLOATS
770+
DatatypeFeatures dtf = ctxt.getDatatypeFeatures();
771+
Boolean dtfState = dtf.getExplicitState(JsonNodeFeature.USE_BIG_DECIMAL_FOR_FLOATS);
772+
boolean useBigDecimal = (dtfState == null) // not explicitly set
773+
? ctxt.isEnabled(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)
774+
: dtfState.booleanValue();
775+
776+
if (useBigDecimal) {
769777
// [databind#4194] Add an option to fail coercing NaN to BigDecimal
770778
// Currently, Jackson 2.x allows such coercion, but Jackson 3.x will not
771779
if (p.isNaN()) {

src/test/java/com/fasterxml/jackson/databind/node/NodeFeaturesTest.java

+54
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.fasterxml.jackson.databind.node;
22

3+
import java.math.BigDecimal;
4+
35
import org.junit.jupiter.api.Test;
46

57
import com.fasterxml.jackson.databind.*;
@@ -156,4 +158,56 @@ public void testWriteSortedProperties() throws Exception
156158
/* Other features
157159
/**********************************************************************
158160
*/
161+
162+
// [databind#4801] USE_BIG_DECIMAL_FOR_FLOATS
163+
@Test
164+
public void testBigDecimalForJsonNodeFeature() throws Exception {
165+
final String JSON = "0.1234567890123456789012345678912345"; // Precision-sensitive
166+
167+
BigDecimal expectedBigDecimal = new BigDecimal("0.1234567890123456789012345678912345"); // Full precision
168+
BigDecimal expectedDoubleLossy = new BigDecimal("0.12345678901234568"); // Precision loss
169+
170+
ObjectMapper mapper;
171+
172+
// Case 1: Both enabled → Should use BigDecimal
173+
mapper = JsonMapper.builder()
174+
.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)
175+
.enable(JsonNodeFeature.USE_BIG_DECIMAL_FOR_FLOATS)
176+
.build();
177+
assertEquals(expectedBigDecimal, mapper.readTree(JSON).decimalValue());
178+
179+
// Case 2: Global enabled, JsonNodeFeature disabled → Should use Double (truncated decimal)
180+
mapper = JsonMapper.builder()
181+
.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)
182+
.disable(JsonNodeFeature.USE_BIG_DECIMAL_FOR_FLOATS)
183+
.build();
184+
assertEquals(expectedDoubleLossy, mapper.readTree(JSON).decimalValue());
185+
186+
// Case 3: Global enabled, JsonNodeFeature undefined → Should use BigDecimal (default to global)
187+
mapper = JsonMapper.builder()
188+
.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)
189+
.build();
190+
assertEquals(expectedBigDecimal, mapper.readTree(JSON).decimalValue());
191+
192+
// Case 4: Global disabled, JsonNodeFeature enabled → Should use BigDecimal
193+
mapper = JsonMapper.builder()
194+
.disable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)
195+
.enable(JsonNodeFeature.USE_BIG_DECIMAL_FOR_FLOATS)
196+
.build();
197+
assertEquals(expectedBigDecimal, mapper.readTree(JSON).decimalValue());
198+
199+
// Case 5: Both disabled → Should use Double (truncated decimal)
200+
mapper = JsonMapper.builder()
201+
.disable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)
202+
.disable(JsonNodeFeature.USE_BIG_DECIMAL_FOR_FLOATS)
203+
.build();
204+
assertEquals(expectedDoubleLossy, mapper.readTree(JSON).decimalValue());
205+
206+
// Case 6: Global disabled, JsonNodeFeature undefined → Should use Double (default to global, truncated decimal)
207+
mapper = JsonMapper.builder()
208+
.disable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)
209+
.build();
210+
assertEquals(expectedDoubleLossy, mapper.readTree(JSON).decimalValue());
211+
}
212+
159213
}

0 commit comments

Comments
 (0)