From 4341ff7240ff319f21d7486bd27cf4aeaed19e12 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Fri, 10 Oct 2025 12:03:55 -0700 Subject: [PATCH 1/7] Add failing tests wrt #5340 --- .../convert/CoerceFloatToIntTest.java | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/src/test/java/tools/jackson/databind/convert/CoerceFloatToIntTest.java b/src/test/java/tools/jackson/databind/convert/CoerceFloatToIntTest.java index ec6169968b..c63b893eb4 100644 --- a/src/test/java/tools/jackson/databind/convert/CoerceFloatToIntTest.java +++ b/src/test/java/tools/jackson/databind/convert/CoerceFloatToIntTest.java @@ -121,6 +121,32 @@ 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)); + } + + // [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)); + } + @Test public void testLegacyFailDoubleToInt() throws Exception { @@ -142,14 +168,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"); From 02b1092e0428c0db9ee2fc74b87b1277066202be Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Fri, 10 Oct 2025 17:49:15 -0700 Subject: [PATCH 2/7] Minor incremental work --- .../databind/deser/jdk/NumberDeserializers.java | 2 +- .../databind/node/TreeTraversingParser.java | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) 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..c460068a53 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[] { 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); From a6d5b176e368ae279bfb5919171f3e80c8fb77d4 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sat, 11 Oct 2025 13:03:03 -0700 Subject: [PATCH 3/7] Minor test extensions --- .../convert/CoerceFloatToIntTest.java | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/test/java/tools/jackson/databind/convert/CoerceFloatToIntTest.java b/src/test/java/tools/jackson/databind/convert/CoerceFloatToIntTest.java index c63b893eb4..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] @@ -132,8 +146,15 @@ public void testLegacyFPToIntCoercionJsonNodeToByte() throws Exception 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 @@ -145,6 +166,13 @@ public void testLegacyFPToIntCoercionJsonNodeToShort() throws Exception 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 From b74fb58d28a38c3c36f152916998a390f3a1217d Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sat, 11 Oct 2025 13:17:00 -0700 Subject: [PATCH 4/7] Fix short/Short handling --- .../databind/deser/jdk/NumberDeserializers.java | 9 ++++++++- .../jackson/databind/deser/std/StdDeserializer.java | 11 ++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) 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 c460068a53..32e323d03f 100644 --- a/src/main/java/tools/jackson/databind/deser/jdk/NumberDeserializers.java +++ b/src/main/java/tools/jackson/databind/deser/jdk/NumberDeserializers.java @@ -368,7 +368,14 @@ protected Short _parseShort(JsonParser p, DeserializationContext ctxt) if (act == CoercionAction.AsEmpty) { return (Short) getEmptyValue(ctxt); } - return p.getShortValue(); + // 11-Oct-2025, tatu: 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..62a48b2794 100644 --- a/src/main/java/tools/jackson/databind/deser/std/StdDeserializer.java +++ b/src/main/java/tools/jackson/databind/deser/std/StdDeserializer.java @@ -601,7 +601,14 @@ protected final short _parseShortPrimitive(JsonParser p, DeserializationContext if (act == CoercionAction.AsEmpty) { return (short) 0; } - return p.getShortValue(); + // 11-Oct-2025, tatu: 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 +683,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); From 2975ed2cf07fdca3256fc4c7163d5d00d05c1d8f Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sat, 11 Oct 2025 13:18:07 -0700 Subject: [PATCH 5/7] ... --- .../tools/jackson/databind/deser/jdk/NumberDeserializers.java | 4 ++-- .../tools/jackson/databind/deser/std/StdDeserializer.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) 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 32e323d03f..2b00dd2bc7 100644 --- a/src/main/java/tools/jackson/databind/deser/jdk/NumberDeserializers.java +++ b/src/main/java/tools/jackson/databind/deser/jdk/NumberDeserializers.java @@ -368,8 +368,8 @@ protected Short _parseShort(JsonParser p, DeserializationContext ctxt) if (act == CoercionAction.AsEmpty) { return (Short) getEmptyValue(ctxt); } - // 11-Oct-2025, tatu: Cumbersome as there is no `getValueAsShort()` that'd avoid - // checks. So need to work around... + // 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 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 62a48b2794..37f76d984b 100644 --- a/src/main/java/tools/jackson/databind/deser/std/StdDeserializer.java +++ b/src/main/java/tools/jackson/databind/deser/std/StdDeserializer.java @@ -601,8 +601,8 @@ protected final short _parseShortPrimitive(JsonParser p, DeserializationContext if (act == CoercionAction.AsEmpty) { return (short) 0; } - // 11-Oct-2025, tatu: Cumbersome as there is no `getValueAsShort()` that'd avoid - // checks. So need to work around... + // 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 From f1674a9f4d35a62772919bafa0b200bea46fdb4a Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sat, 11 Oct 2025 13:19:34 -0700 Subject: [PATCH 6/7] And then byte/Byte handling --- .../jackson/databind/deser/jdk/NumberDeserializers.java | 9 ++++++++- .../jackson/databind/deser/std/StdDeserializer.java | 9 ++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) 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 2b00dd2bc7..ce2303f14c 100644 --- a/src/main/java/tools/jackson/databind/deser/jdk/NumberDeserializers.java +++ b/src/main/java/tools/jackson/databind/deser/jdk/NumberDeserializers.java @@ -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: 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 37f76d984b..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: From 2ed454423ea4f18bcaaaa2d5cb48b5353ae00d9a Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sat, 11 Oct 2025 13:56:31 -0700 Subject: [PATCH 7/7] Add release notes --- release-notes/CREDITS | 8 ++++++++ release-notes/VERSION | 6 ++++++ 2 files changed, 14 insertions(+) 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`