(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