Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions release-notes/CREDITS
Original file line number Diff line number Diff line change
Expand Up @@ -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]
6 changes: 6 additions & 0 deletions release-notes/VERSION
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
*/
public class NumberDeserializers
{
private final static HashSet<String> _classNames = new HashSet<String>();
private final static HashSet<String> _classNames = new HashSet<>();
static {
// note: can skip primitive types; other ways to check them:
Class<?>[] numberTypes = new Class<?>[] {
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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]
Expand All @@ -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
{
Expand All @@ -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");
Expand Down