(continuation of #5319 )
The DeserializationFeature.ACCEPT_FLOAT_AS_INT
configuration is not respected when deserializing from a JsonNode
to byte
or short
types using TreeTraversingParser
. This issue does NOT affect int
or long
types, which work correctly. The feature works correctly for all integral types when deserializing directly from a string or stream.
Version Information
3.0.0
Reproduction
import tools.jackson.databind.DeserializationFeature;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.json.JsonMapper;
public class AcceptFloatAsIntBugDemo {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = JsonMapper.builder()
.enable(DeserializationFeature.ACCEPT_FLOAT_AS_INT)
.build();
// Test 1: Direct deserialization from string - WORKS
System.out.println("Test 1: Direct string to byte");
try {
byte result = mapper.readValue("1.1", byte.class);
System.out.println(" ✓ Success: " + result);
} catch (Exception e) {
System.out.println(" ✗ Failed: " + e.getMessage());
}
// Test 2: Deserialization from JsonNode to byte - FAILS
System.out.println("\nTest 2: JsonNode to byte");
try {
JsonNode node = mapper.readTree("{\"value\": 1.1}");
JsonNode valueNode = node.get("value");
byte result = mapper.readerFor(byte.class).readValue(valueNode);
System.out.println(" ✓ Success: " + result);
} catch (Exception e) {
System.out.println(" ✗ Failed: " + e.getMessage());
}
// Test 3: JsonNode to short - FAILS
System.out.println("\nTest 3: JsonNode to short");
try {
JsonNode node = mapper.readTree("{\"value\": 1.1}");
JsonNode valueNode = node.get("value");
short result = mapper.readerFor(short.class).readValue(valueNode);
System.out.println(" ✓ Success: " + result);
} catch (Exception e) {
System.out.println(" ✗ Failed: " + e.getMessage());
}
// Test 4: JsonNode to int - WORKS
System.out.println("\nTest 4: JsonNode to int");
try {
JsonNode node = mapper.readTree("{\"value\": 1.1}");
JsonNode valueNode = node.get("value");
int result = mapper.readerFor(int.class).readValue(valueNode);
System.out.println(" ✓ Success: " + result);
} catch (Exception e) {
System.out.println(" ✗ Failed: " + e.getMessage());
}
// Test 5: JsonNode to long - WORKS
System.out.println("\nTest 5: JsonNode to long");
try {
JsonNode node = mapper.readTree("{\"value\": 1.1}");
JsonNode valueNode = node.get("value");
long result = mapper.readerFor(long.class).readValue(valueNode);
System.out.println(" ✓ Success: " + result);
} catch (Exception e) {
System.out.println(" ✗ Failed: " + e.getMessage());
}
// Test 6: Workaround - convert JsonNode to string first - WORKS
System.out.println("\nTest 6: JsonNode -> String -> byte (workaround)");
try {
JsonNode node = mapper.readTree("{\"value\": 1.1}");
JsonNode valueNode = node.get("value");
byte result = mapper.readerFor(byte.class).readValue(valueNode.toString());
System.out.println(" ✓ Success: " + result);
} catch (Exception e) {
System.out.println(" ✗ Failed: " + e.getMessage());
}
}
}
Expected behavior
All the tests work, but actual output is
Test 1: Direct string to byte
✓ Success: 1
Test 2: JsonNode to byte
✗ Failed: Numeric value (1.1) of `DoubleNode` has fractional part; cannot convert to `int`
Test 3: JsonNode to short
✗ Failed: Numeric value (1.1) of `DoubleNode` has fractional part; cannot convert to `int`
Test 4: JsonNode to int
✓ Success: 1
Test 5: JsonNode to long
✓ Success: 1
Test 6: JsonNode -> String -> byte (workaround)
✓ Success: 1
Additional context
Stack Trace
tools.jackson.core.exc.InputCoercionException: Numeric value (1.1) of `DoubleNode` has fractional part; cannot convert to `int`
at [No location information]
at tools.jackson.core.base.ParserMinimalBase._constructInputCoercion(ParserMinimalBase.java:1130)
at tools.jackson.databind.node.TreeTraversingParser.getIntValue(TreeTraversingParser.java:303)
at tools.jackson.core.base.ParserMinimalBase.getByteValue(ParserMinimalBase.java:576)
at tools.jackson.databind.deser.std.StdDeserializer._parseBytePrimitive(StdDeserializer.java:527)
at tools.jackson.databind.deser.jdk.NumberDeserializers$ByteDeserializer.deserialize(NumberDeserializers.java:259)
Root Cause
The issue appears to be in TreeTraversingParser.getIntValue()
which doesn't check or respect the ACCEPT_FLOAT_AS_INT
feature when reading from numeric nodes. The error message reveals the problem:
Numeric value (1.1) of `DoubleNode` has fractional part; cannot convert to `int`
Notice it says "cannot convert to int
" even when deserializing to byte
or short
. The stack trace shows:
at tools.jackson.databind.node.TreeTraversingParser.getIntValue(TreeTraversingParser.java:303)
at tools.jackson.core.base.ParserMinimalBase.getByteValue(ParserMinimalBase.java:576)
The methods getByteValue()
and getShortValue()
internally call getIntValue()
first, which validates that the number can be converted to an exact integral value without checking the ACCEPT_FLOAT_AS_INT
feature.
By contrast, deserialization to int
and long
appears to have proper feature checking, which is why those work correctly.
Comment From: cowtowncoder
Note: fix for int
, long
, BigInteger
via #5322.
Comment From: cowtowncoder
Fixed for 3.0.1