diff --git a/release-notes/CREDITS b/release-notes/CREDITS index 2950df02c5..0f593827f3 100644 --- a/release-notes/CREDITS +++ b/release-notes/CREDITS @@ -72,6 +72,14 @@ David Nault (@dnault) * Requested #5244: Support accessing annotations via `AnnotatedMember.annotations()` [3.0.0] +Artur (@Artur-) + * Reported #5319: `TreeTraversingParser` does not respect + `DeserializationFeature.ACCEPT_FLOAT_AS_INT` + [3.0.0] + * Reported #5340: `DeserializationFeature.ACCEPT_FLOAT_AS_INT` not respected for byte/short + when deserializing from JsonNode + [3.0.1] + Dónal Murtagh (@donalmurtagh) * Reported #5323: `UUID` serialization is broken in v3.0.0-rc9 [3.0.0] diff --git a/release-notes/VERSION b/release-notes/VERSION index e7d6ebd783..12593a2aba 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -5,6 +5,12 @@ Versions: 3.x (for earlier see VERSION-2.x) === Releases === ------------------------------------------------------------------------ +3.0.1 (not yet released) + +#5340: `DeserializationFeature.ACCEPT_FLOAT_AS_INT` not respected for byte/short + when deserializing from JsonNode + (reported by @Artur) + 3.0.0 (03-Oct-2025) #5319: `TreeTraversingParser` does not respect `DeserializationFeature.ACCEPT_FLOAT_AS_INT` diff --git a/src/main/java/tools/jackson/databind/deser/jdk/NumberDeserializers.java b/src/main/java/tools/jackson/databind/deser/jdk/NumberDeserializers.java index eb34d5ca0a..ce2303f14c 100644 --- a/src/main/java/tools/jackson/databind/deser/jdk/NumberDeserializers.java +++ b/src/main/java/tools/jackson/databind/deser/jdk/NumberDeserializers.java @@ -26,7 +26,7 @@ */ public class NumberDeserializers { - private final static HashSet _classNames = new HashSet(); + private final static HashSet _classNames = new HashSet<>(); static { // note: can skip primitive types; other ways to check them: Class[] numberTypes = new Class[] { @@ -278,7 +278,14 @@ protected Byte _parseByte(JsonParser p, DeserializationContext ctxt) if (act == CoercionAction.AsEmpty) { return (Byte) getEmptyValue(ctxt); } - return p.getByteValue(); + // 11-Oct-2025, tatu: [databind#5240] Cumbersome as there is no + // `getValueAsByte()` that'd avoid checks. So need to work around. + int i = p.getValueAsInt(); + if (_shortOverflow(i)) { + // Let's trigger overflow handling + return p.getByteValue(); + } + return (byte) i; case JsonTokenId.ID_NULL: // null fine for non-primitive return (Byte) getNullValue(ctxt); case JsonTokenId.ID_NUMBER_INT: @@ -368,7 +375,14 @@ protected Short _parseShort(JsonParser p, DeserializationContext ctxt) if (act == CoercionAction.AsEmpty) { return (Short) getEmptyValue(ctxt); } - return p.getShortValue(); + // 11-Oct-2025, tatu: [databind#5240] Cumbersome as there is no + // `getValueAsShort()` that'd avoid checks. So need to work around. + int i = p.getValueAsInt(); + if (_shortOverflow(i)) { + // Let's trigger overflow handling + return p.getShortValue(); + } + return (short) i; case JsonTokenId.ID_NULL: // null fine for non-primitive return (Short) getNullValue(ctxt); case JsonTokenId.ID_NUMBER_INT: diff --git a/src/main/java/tools/jackson/databind/deser/std/StdDeserializer.java b/src/main/java/tools/jackson/databind/deser/std/StdDeserializer.java index 3aa05c37a2..fbee204ca5 100644 --- a/src/main/java/tools/jackson/databind/deser/std/StdDeserializer.java +++ b/src/main/java/tools/jackson/databind/deser/std/StdDeserializer.java @@ -524,7 +524,14 @@ protected final byte _parseBytePrimitive(JsonParser p, DeserializationContext ct if (act == CoercionAction.AsEmpty) { return (byte) 0; } - return p.getByteValue(); + // 11-Oct-2025, tatu: [databind#5240] Cumbersome as there is no + // `getValueAsByte()` that'd avoid checks. So need to work around. + int i = p.getValueAsInt(); + if (_shortOverflow(i)) { + // Let's trigger overflow handling + return p.getByteValue(); + } + return (byte) i; case JsonTokenId.ID_NUMBER_INT: return p.getByteValue(); case JsonTokenId.ID_NULL: @@ -601,7 +608,14 @@ protected final short _parseShortPrimitive(JsonParser p, DeserializationContext if (act == CoercionAction.AsEmpty) { return (short) 0; } - return p.getShortValue(); + // 11-Oct-2025, tatu: [databind#5240] Cumbersome as there is no + // `getValueAsShort()` that'd avoid checks. So need to work around. + int i = p.getValueAsInt(); + if (_shortOverflow(i)) { + // Let's trigger overflow handling + return p.getShortValue(); + } + return (short) i; case JsonTokenId.ID_NUMBER_INT: return p.getShortValue(); case JsonTokenId.ID_NULL: @@ -676,8 +690,10 @@ protected int _parseIntPrimitive(JsonParser p, DeserializationContext ctxt) if (act == CoercionAction.AsEmpty) { return 0; } + // Important! Must use coercing conversion method here: return p.getValueAsInt(); case JsonTokenId.ID_NUMBER_INT: + // Here regular (strict) accessor is fine return p.getIntValue(); case JsonTokenId.ID_NULL: _verifyNullForPrimitive(ctxt); diff --git a/src/main/java/tools/jackson/databind/node/TreeTraversingParser.java b/src/main/java/tools/jackson/databind/node/TreeTraversingParser.java index a2310e66c5..2f5116c1e5 100644 --- a/src/main/java/tools/jackson/databind/node/TreeTraversingParser.java +++ b/src/main/java/tools/jackson/databind/node/TreeTraversingParser.java @@ -294,6 +294,23 @@ public float getFloatValue() throws InputCoercionException { return (float) currentNumericNode(NR_FLOAT).doubleValue(); } + @Override + public short getShortValue() throws InputCoercionException { + final NumericNode node = (NumericNode) currentNumericNode(NR_INT); + if (!node.canConvertToShort()) { + String desc = _longIntegerDesc(node.asString()); + if (!node.canConvertToExactIntegral()) { + throw _constructInputCoercion(String.format( +"Numeric value (%s) of `%s` has fractional part; cannot convert to `short`", + desc, node.getClass().getSimpleName()), + node.asToken(), Integer.TYPE); + } + // otherwise assume range overflow + _reportOverflowShort(desc, currentToken()); + } + return node.shortValue(); + } + @Override public int getIntValue() throws InputCoercionException { final NumericNode node = (NumericNode) currentNumericNode(NR_INT); diff --git a/src/test/java/tools/jackson/databind/convert/CoerceFloatToIntTest.java b/src/test/java/tools/jackson/databind/convert/CoerceFloatToIntTest.java index ec6169968b..aac26174da 100644 --- a/src/test/java/tools/jackson/databind/convert/CoerceFloatToIntTest.java +++ b/src/test/java/tools/jackson/databind/convert/CoerceFloatToIntTest.java @@ -93,6 +93,13 @@ public void testLegacyDoubleToIntCoercionJsonNodeToInteger() throws Exception DEFAULT_MAPPER.treeToValue(nodeF.numberNode(-2.5f), Integer.class)); assertEquals(3, DEFAULT_MAPPER.treeToValue(nodeF.numberNode(BigDecimal.valueOf(3.75)), Integer.class)); + + assertEquals(1, + DEFAULT_MAPPER.treeToValue(nodeF.numberNode(1.25), Integer.TYPE)); + assertEquals(-2, + DEFAULT_MAPPER.treeToValue(nodeF.numberNode(-2.5f), Integer.TYPE)); + assertEquals(3, + DEFAULT_MAPPER.treeToValue(nodeF.numberNode(BigDecimal.valueOf(3.75)), Integer.TYPE)); } // [databind#5319] @@ -106,6 +113,13 @@ public void testLegacyDoubleToIntCoercionJsonNodeToLong() throws Exception DEFAULT_MAPPER.treeToValue(nodeF.numberNode(-2.5f), Long.class)); assertEquals(3L, DEFAULT_MAPPER.treeToValue(nodeF.numberNode(BigDecimal.valueOf(3.75)), Long.class)); + + assertEquals(1L, + DEFAULT_MAPPER.treeToValue(nodeF.numberNode(1.25), Long.TYPE)); + assertEquals(-2L, + DEFAULT_MAPPER.treeToValue(nodeF.numberNode(-2.5f), Long.TYPE)); + assertEquals(3L, + DEFAULT_MAPPER.treeToValue(nodeF.numberNode(BigDecimal.valueOf(3.75)), Long.TYPE)); } // [databind#5319] @@ -121,6 +135,46 @@ public void testLegacyDoubleToIntCoercionJsonNodeToBigInteger() throws Exception DEFAULT_MAPPER.treeToValue(nodeF.numberNode(BigDecimal.valueOf(3.75)), BigInteger.class)); } + // [databind#5340] + @Test + public void testLegacyFPToIntCoercionJsonNodeToByte() throws Exception + { + final JsonNodeFactory nodeF = DEFAULT_MAPPER.getNodeFactory(); + assertEquals((byte) 1, + DEFAULT_MAPPER.treeToValue(nodeF.numberNode(1.25), Byte.class)); + assertEquals((byte) -2, + DEFAULT_MAPPER.treeToValue(nodeF.numberNode(-2.5f), Byte.class)); + assertEquals((byte) 3, + DEFAULT_MAPPER.treeToValue(nodeF.numberNode(BigDecimal.valueOf(3.75)), Byte.class)); + + assertEquals((byte) 1, + DEFAULT_MAPPER.treeToValue(nodeF.numberNode(1.25), Byte.TYPE)); + assertEquals((byte) -2, + DEFAULT_MAPPER.treeToValue(nodeF.numberNode(-2.5f), Byte.TYPE)); + assertEquals((byte) 3, + DEFAULT_MAPPER.treeToValue(nodeF.numberNode(BigDecimal.valueOf(3.75)), Byte.TYPE)); + } + + // [databind#5340] + @Test + public void testLegacyFPToIntCoercionJsonNodeToShort() throws Exception + { + final JsonNodeFactory nodeF = DEFAULT_MAPPER.getNodeFactory(); + assertEquals((short) 1, + DEFAULT_MAPPER.treeToValue(nodeF.numberNode(1.25), Short.class)); + assertEquals((short) -2, + DEFAULT_MAPPER.treeToValue(nodeF.numberNode(-2.5f), Short.class)); + assertEquals((short) 3, + DEFAULT_MAPPER.treeToValue(nodeF.numberNode(BigDecimal.valueOf(3.75)), Short.class)); + + assertEquals((short) 1, + DEFAULT_MAPPER.treeToValue(nodeF.numberNode(1.25), Short.TYPE)); + assertEquals((short) -2, + DEFAULT_MAPPER.treeToValue(nodeF.numberNode(-2.5f), Short.TYPE)); + assertEquals((short) 3, + DEFAULT_MAPPER.treeToValue(nodeF.numberNode(BigDecimal.valueOf(3.75)), Short.TYPE)); + } + @Test public void testLegacyFailDoubleToInt() throws Exception { @@ -142,14 +196,14 @@ public void testLegacyFailDoubleToLong() throws Exception @Test public void testLegacyFailDoubleToOther() throws Exception { - _verifyCoerceFail(READER_LEGACY_FAIL, Short.class, "0.5"); - _verifyCoerceFail(READER_LEGACY_FAIL, Short.TYPE, "-2.5"); - _verifyCoerceFail(READER_LEGACY_FAIL, short[].class, "[ -1.35 ]", "to `short` value"); - _verifyCoerceFail(READER_LEGACY_FAIL, Byte.class, "0.5"); _verifyCoerceFail(READER_LEGACY_FAIL, Byte.TYPE, "-2.5"); _verifyCoerceFail(READER_LEGACY_FAIL, byte[].class, "[ -1.35 ]", "to `byte` value"); + _verifyCoerceFail(READER_LEGACY_FAIL, Short.class, "0.5"); + _verifyCoerceFail(READER_LEGACY_FAIL, Short.TYPE, "-2.5"); + _verifyCoerceFail(READER_LEGACY_FAIL, short[].class, "[ -1.35 ]", "to `short` value"); + _verifyCoerceFail(READER_LEGACY_FAIL, BigInteger.class, "25236.256"); _verifyCoerceFail(READER_LEGACY_FAIL, AtomicLong.class, "25236.256");