Skip to content

TreeTraversingParser does not respect DeserializationFeature.ACCEPT_FLOAT_AS_INT #5319

@Artur-

Description

@Artur-

Search before asking

  • I searched in the issues and found nothing similar.

Describe the bug

TreeTraversingParser in Jackson 3.0.0-rc9 does not respect the DeserializationFeature.ACCEPT_FLOAT_AS_INT setting when deserializing from JsonNode objects, even though the feature is enabled and works correctly when deserializing from JSON strings.

When DeserializationFeature.ACCEPT_FLOAT_AS_INT is enabled (which is the default in Jackson 3), deserializing a floating-point number to an integer type should truncate the decimal part. This works correctly when deserializing from JSON strings, but fails when deserializing from JsonNode objects.

The issue is in TreeTraversingParser.getIntValue() method which explicitly checks for fractional parts and throws an exception, regardless of the ACCEPT_FLOAT_AS_INT configuration.

Version Information

  • Jackson version: 3.0.0-rc9
  • Java version: 21
  • Operating System: macOS

Reproduction

import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.DeserializationFeature;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.json.JsonMapper;

public class Jackson3FloatToIntBug {
    static class TestBean {
        public int value;
    }
    
    public static void main(String[] args) throws Exception {
        ObjectMapper mapper = JsonMapper.builder()
            .enable(DeserializationFeature.ACCEPT_FLOAT_AS_INT)  // Explicitly enable (though it's default)
            .build();
            
        System.out.println("ACCEPT_FLOAT_AS_INT enabled: " + 
            mapper.isEnabled(DeserializationFeature.ACCEPT_FLOAT_AS_INT));
        
        // Test 1: Direct JSON string parsing - WORKS
        try {
            TestBean bean1 = mapper.readValue("{\"value\": 1.9}", TestBean.class);
            System.out.println("From JSON string: " + bean1.value + " (SUCCESS)");
        } catch (Exception e) {
            System.out.println("From JSON string: FAILED - " + e.getMessage());
        }
        
        // Test 2: Via JsonNode - FAILS
        try {
            JsonNode node = mapper.readTree("{\"value\": 1.9}");
            TestBean bean2 = mapper.treeToValue(node, TestBean.class);
            System.out.println("From JsonNode: " + bean2.value + " (SUCCESS)");
        } catch (Exception e) {
            System.out.println("From JsonNode: FAILED - " + e.getMessage());
        }
        
        // Test 3: Via ObjectReader with JsonNode - FAILS
        try {
            JsonNode node = mapper.readTree("{\"value\": 1.9}");
            JsonNode valueNode = node.get("value");
            int value = mapper.readerFor(int.class).readValue(valueNode);
            System.out.println("From JsonNode field: " + value + " (SUCCESS)");
        } catch (Exception e) {
            System.out.println("From JsonNode field: FAILED - " + e.getMessage());
        }
    }
}

Expected behavior

All three tests should succeed and convert 1.9 to 1 (truncating the decimal part) since ACCEPT_FLOAT_AS_INT is enabled.

Actual behavior:

ACCEPT_FLOAT_AS_INT enabled: true
From JSON string: 1 (SUCCESS)
From JsonNode: FAILED - Numeric value (1.9) of `DoubleNode` has fractional part; cannot convert to `int` (through reference chain: TestBean["value"])
From JsonNode field: FAILED - Numeric value (1.9) out of range of `int` (-2147483648 - 2147483647)

The same test with Jackson 2:

ACCEPT_FLOAT_AS_INT enabled: true
From JSON string: 1 (SUCCESS)
From JsonNode: 1 (SUCCESS)
From JsonNode field: 1 (SUCCESS)

Additional context

Root Cause Analysis

The issue is in tools.jackson.databind.node.TreeTraversingParser.getIntValue() (around line 298-309):

@Override
public int getIntValue() throws InputCoercionException {
    final NumericNode node = (NumericNode) currentNumericNode(NR_INT);
    if (!node.canConvertToInt()) {
        // [databind#5309] Misleading exception message for DoubleNode to `int` value conversion
        if (!node.canConvertToExactIntegral()) {
            throw _constructInputCoercion(String.format(
                "Numeric value (%s) of `%s` has fractional part; cannot convert to `int`",
                _longIntegerDesc(node.asString()),
                node.getClass().getSimpleName()
            ),
            node.asToken(), Integer.TYPE);
        }
        // ... overflow handling
    }
    return node.intValue();
}

This method checks canConvertToExactIntegral() which returns false for nodes with fractional parts, and immediately throws an exception without consulting the ACCEPT_FLOAT_AS_INT feature flag.

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.0Issue planned for initial 3.0 releasehas-failing-testIndicates that there exists a test case (under `failing/`) to reproduce the issue

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions