diff --git a/src/main/java/tools/jackson/databind/JsonNode.java b/src/main/java/tools/jackson/databind/JsonNode.java index 0d828531a0..7f37df8a35 100644 --- a/src/main/java/tools/jackson/databind/JsonNode.java +++ b/src/main/java/tools/jackson/databind/JsonNode.java @@ -461,6 +461,19 @@ public final boolean isBinary() { return getNodeType() == JsonNodeType.BINARY; } + /** + * Method that can be used to check whether this node is a numeric + * node ({@link #isNumber} would return true) + * AND can be converted without loss to {@code short} (that is, its value fits + * in Java's 16-bit signed integer type, {@code short} and + * if it is a floating-point number, it does not have fractional part). + *

+ * NOTE: this method does not consider possible value type conversion + * from non-number types like JSON String into Number; so even if this method returns false, + * it is possible that {@link #asShort} could still succeed. + */ + public boolean canConvertToShort() { return false; } + /** * Method that can be used to check whether this node is a numeric * node ({@link #isNumber} would return true) @@ -730,6 +743,51 @@ public String asText(String defaultValue) { */ public abstract short shortValue(); + /** + * Method similar to {@link #shortValue()}, but that will return specified + * {@code defaultValue} if this node cannot be converted to Java {@code short}. + * + * @param defaultValue Value to return if this node cannot be converted to Java {@code short} + * + * @return Java {@code short} value this node represents, if possible to accurately represent; + * {@code defaultValue} otherwise + */ + public abstract short shortValue(short defaultValue); + + /** + * Method similar to {@link #shortValue()} but in addition to coercing Number + * values (same as {@link #shortValue()}), will also try to coerce a + * couple of additional types (or cases): + *

+ * + * @return {@code short} value this node represents, if possible to accurately represent + * + * @throws JsonNodeException if node value cannot be converted to {@code short} + */ + public abstract short asShort(); + + /** + * Method similar to {@link #shortValue()}, but that will return specified + * {@code defaultValue} if this node cannot be converted to {@code short}. + * + * @param defaultValue Value to return if this node cannot be converted to {@code short} + * + * @return {@code short} value this node represents, if possible to accurately represent; + * {@code defaultValue} otherwise + */ + public abstract short asShort(short defaultValue); + // // Scalar access: Numbers, Java int /** @@ -1013,6 +1071,45 @@ public String asText(String defaultValue) { */ public abstract float floatValue(); + /** + * Method similar to {@link #floatValue()}, but that will return specified + * {@code defaultValue} if this node cannot be converted to {@code float}. + * + * @param defaultValue Value to return if this node cannot be converted to {@code float} + * + * @return {@code float} value this node represents, if possible to accurately represent; + * {@code defaultValue} otherwise + */ + public abstract float floatValue(float defaultValue); + + /** + * Method similar to {@link #floatValue()} but in addition to coercing Number + * values will also try coerce couple of additional types: + * + *

+ * + * @return {@code float} value this node represents, if possible to accurately represent + * + * @throws JsonNodeException if node value cannot be converted to {@code float} + */ + public abstract float asFloat(); + + /** + * Method similar to {@link #asFloat()}, but that will return {@code defaultValue} + * if this node cannot be coerced to {@code float}. + * + * @return {@code float} value this node represents, + * if possible to accurately represent; {@code defaultValue} otherwise + */ + public abstract float asFloat(float defaultValue); + // // Scalar access: Numbers, Java double /** diff --git a/src/main/java/tools/jackson/databind/node/BaseJsonNode.java b/src/main/java/tools/jackson/databind/node/BaseJsonNode.java index 9dea2cc8dc..1e7b287875 100644 --- a/src/main/java/tools/jackson/databind/node/BaseJsonNode.java +++ b/src/main/java/tools/jackson/databind/node/BaseJsonNode.java @@ -70,6 +70,23 @@ public short shortValue() { return _reportCoercionFail("shortValue()", Short.TYPE, "value type not numeric"); } + @Override + public short shortValue(short defaultValue) { + // Overridden by NumericNode, for other types return default + return defaultValue; + } + + @Override + public short asShort() { + return _reportCoercionFail("asShort()", Short.TYPE, "value type not numeric"); + } + + @Override + public short asShort(short defaultValue) { + // Overridden by NumericNode, for other types return default + return defaultValue; + } + @Override public int intValue() { return _reportCoercionFail("intValue()", Integer.TYPE, "value type not numeric"); @@ -177,6 +194,23 @@ public float floatValue() { return _reportCoercionFail("floatValue()", Float.TYPE, "value type not numeric"); } + @Override + public float floatValue(float defaultValue) { + // Overridden by NumericNode, for other types return default + return defaultValue; + } + + @Override + public float asFloat() { + return _reportCoercionFail("asFloat()", Float.TYPE, "value type not numeric"); + } + + @Override + public float asFloat(float defaultValue) { + // Overridden by NumericNode, for other types return default + return defaultValue; + } + @Override public double doubleValue() { return _reportCoercionFail("doubleValue()", Double.TYPE, "value type not numeric"); diff --git a/src/main/java/tools/jackson/databind/node/BigIntegerNode.java b/src/main/java/tools/jackson/databind/node/BigIntegerNode.java index e44ea081f7..80fbb2eca7 100644 --- a/src/main/java/tools/jackson/databind/node/BigIntegerNode.java +++ b/src/main/java/tools/jackson/databind/node/BigIntegerNode.java @@ -19,11 +19,6 @@ public class BigIntegerNode { private static final long serialVersionUID = 3L; - private final static BigInteger MIN_INTEGER = BigInteger.valueOf(Integer.MIN_VALUE); - private final static BigInteger MAX_INTEGER = BigInteger.valueOf(Integer.MAX_VALUE); - private final static BigInteger MIN_LONG = BigInteger.valueOf(Long.MIN_VALUE); - private final static BigInteger MAX_LONG = BigInteger.valueOf(Long.MAX_VALUE); - final protected BigInteger _value; /* @@ -72,6 +67,32 @@ public Number numberValue() { return _value; } + @Override + public short shortValue() { + if (_inShortRange()) { + return (short) _value.intValue(); + } + return _reportShortCoercionRangeFail("shortValue()"); + } + + @Override + public short shortValue(short defaultValue) { + return _inShortRange() ? (short) _value.intValue() : defaultValue; + } + + @Override + public short asShort() { + if (_inShortRange()) { + return (short) _value.intValue(); + } + return _reportShortCoercionRangeFail("asShort()"); + } + + @Override + public short asShort(short defaultValue) { + return _inShortRange() ? (short) _value.intValue() : defaultValue; + } + @Override public int intValue() { if (_inIntRange()) { @@ -166,6 +187,27 @@ public float floatValue() { return _reportFloatCoercionRangeFail("floatValue()"); } + @Override + public float floatValue(float defaultValue) { + float f = _asFloatValueUnchecked(); + return (Float.isFinite(f)) ? f : defaultValue; + } + + @Override + public float asFloat() { + float f = _asFloatValueUnchecked(); + if (Float.isFinite(f)) { + return f; + } + return _reportFloatCoercionRangeFail("asFloat()"); + } + + @Override + public float asFloat(float defaultValue) { + float f = _asFloatValueUnchecked(); + return (Float.isFinite(f)) ? f : defaultValue; + } + @Override public double doubleValue() { double d = _asDoubleValueUnchecked(); @@ -252,23 +294,20 @@ protected double _asDoubleValueUnchecked() { @Override protected boolean _inShortRange() { - if (_inIntRange()) { - int v = _value.intValue(); - return (v >= Short.MIN_VALUE && v <= Short.MAX_VALUE); - } - return false; + return (_value.compareTo(BI_MIN_SHORT) >= 0) + && (_value.compareTo(BI_MAX_SHORT) <= 0); } @Override public boolean _inIntRange() { - return (_value.compareTo(MIN_INTEGER) >= 0) - && (_value.compareTo(MAX_INTEGER) <= 0); + return (_value.compareTo(BI_MIN_INTEGER) >= 0) + && (_value.compareTo(BI_MAX_INTEGER) <= 0); } @Override protected boolean _inLongRange() { - return (_value.compareTo(MIN_LONG) >= 0) - && (_value.compareTo(MAX_LONG) <= 0); + return (_value.compareTo(BI_MIN_LONG) >= 0) + && (_value.compareTo(BI_MAX_LONG) <= 0); } /* diff --git a/src/main/java/tools/jackson/databind/node/BooleanNode.java b/src/main/java/tools/jackson/databind/node/BooleanNode.java index e21c0f64de..09e252a7b7 100644 --- a/src/main/java/tools/jackson/databind/node/BooleanNode.java +++ b/src/main/java/tools/jackson/databind/node/BooleanNode.java @@ -111,11 +111,6 @@ protected String _asString() { return _value ? "true" : "false"; } - @Override - public long asLong(long defaultValue) { - return _value ? 1L : 0L; - } - /* /********************************************************************** /* Overridden JsonNode methods, other diff --git a/src/main/java/tools/jackson/databind/node/DecimalNode.java b/src/main/java/tools/jackson/databind/node/DecimalNode.java index ea92a96a6f..1b4d7f22af 100644 --- a/src/main/java/tools/jackson/databind/node/DecimalNode.java +++ b/src/main/java/tools/jackson/databind/node/DecimalNode.java @@ -20,13 +20,6 @@ public class DecimalNode public static final DecimalNode ZERO = new DecimalNode(BigDecimal.ZERO); - private final static BigDecimal MIN_SHORT = BigDecimal.valueOf(Short.MIN_VALUE); - private final static BigDecimal MAX_SHORT = BigDecimal.valueOf(Short.MAX_VALUE); - private final static BigDecimal MIN_INTEGER = BigDecimal.valueOf(Integer.MIN_VALUE); - private final static BigDecimal MAX_INTEGER = BigDecimal.valueOf(Integer.MAX_VALUE); - private final static BigDecimal MIN_LONG = BigDecimal.valueOf(Long.MIN_VALUE); - private final static BigDecimal MAX_LONG = BigDecimal.valueOf(Long.MAX_VALUE); - final protected BigDecimal _value; /* @@ -87,6 +80,33 @@ public float floatValue() { return _reportFloatCoercionRangeFail("floatValue()"); } + @Override + public float floatValue(float defaultValue) { + float f = _value.floatValue(); + if (Float.isFinite(f)) { + return f; + } + return defaultValue; + } + + @Override + public float asFloat() { + float f = _value.floatValue(); + if (Float.isFinite(f)) { + return f; + } + return _reportFloatCoercionRangeFail("asFloat()"); + } + + @Override + public float asFloat(float defaultValue) { + float f = _value.floatValue(); + if (Float.isFinite(f)) { + return f; + } + return defaultValue; + } + @Override public double doubleValue() { double d = _value.doubleValue(); @@ -201,17 +221,17 @@ protected boolean _hasFractionalPart() { @Override protected boolean _inShortRange() { - return (_value.compareTo(MIN_SHORT) >= 0) && (_value.compareTo(MAX_SHORT) <= 0); + return (_value.compareTo(BD_MIN_SHORT) >= 0) && (_value.compareTo(BD_MAX_SHORT) <= 0); } @Override protected boolean _inIntRange() { - return (_value.compareTo(MIN_INTEGER) >= 0) && (_value.compareTo(MAX_INTEGER) <= 0); + return _inLongRange() && (_value.compareTo(BD_MIN_INTEGER) >= 0) && (_value.compareTo(BD_MAX_INTEGER) <= 0); } @Override protected boolean _inLongRange() { - return (_value.compareTo(MIN_LONG) >= 0) && (_value.compareTo(MAX_LONG) <= 0); + return (_value.compareTo(BD_MIN_LONG) >= 0) && (_value.compareTo(BD_MAX_LONG) <= 0); } /* diff --git a/src/main/java/tools/jackson/databind/node/DoubleNode.java b/src/main/java/tools/jackson/databind/node/DoubleNode.java index 3804f6166e..d2334e7d91 100644 --- a/src/main/java/tools/jackson/databind/node/DoubleNode.java +++ b/src/main/java/tools/jackson/databind/node/DoubleNode.java @@ -77,6 +77,33 @@ public float floatValue() { return _reportFloatCoercionRangeFail("floatValue()"); } + @Override + public float floatValue(float defaultValue) { + float f = (float) _value; + if (Float.isFinite(f)) { + return f; + } + return defaultValue; + } + + @Override + public float asFloat() { + float f = (float) _value; + if (Float.isFinite(f)) { + return f; + } + return _reportFloatCoercionRangeFail("asFloat()"); + } + + @Override + public float asFloat(float defaultValue) { + float f = (float) _value; + if (Float.isFinite(f)) { + return f; + } + return defaultValue; + } + @Override public double doubleValue() { return _value; diff --git a/src/main/java/tools/jackson/databind/node/FloatNode.java b/src/main/java/tools/jackson/databind/node/FloatNode.java index b8c0609b1b..467ea2a1e4 100644 --- a/src/main/java/tools/jackson/databind/node/FloatNode.java +++ b/src/main/java/tools/jackson/databind/node/FloatNode.java @@ -67,6 +67,21 @@ public float floatValue() { return _value; } + @Override + public float floatValue(float defaultValue) { + return _value; + } + + @Override + public float asFloat() { + return _value; + } + + @Override + public float asFloat(float defaultValue) { + return _value; + } + @Override public double doubleValue() { return _value; diff --git a/src/main/java/tools/jackson/databind/node/IntNode.java b/src/main/java/tools/jackson/databind/node/IntNode.java index bdad938678..c5fde896b4 100644 --- a/src/main/java/tools/jackson/databind/node/IntNode.java +++ b/src/main/java/tools/jackson/databind/node/IntNode.java @@ -82,6 +82,32 @@ public Number numberValue() { return Integer.valueOf(_value); } + @Override + public short shortValue() { + if (_inShortRange()) { + return (short) _value; + } + return _reportShortCoercionRangeFail("shortValue()"); + } + + @Override + public short shortValue(short defaultValue) { + return _inShortRange() ? (short) _value : defaultValue; + } + + @Override + public short asShort() { + if (_inShortRange()) { + return (short) _value; + } + return _reportShortCoercionRangeFail("asShort()"); + } + + @Override + public short asShort(short defaultValue) { + return _inShortRange() ? (short) _value : defaultValue; + } + @Override public int intValue() { return _value; } diff --git a/src/main/java/tools/jackson/databind/node/LongNode.java b/src/main/java/tools/jackson/databind/node/LongNode.java index 052375494f..9cfda12086 100644 --- a/src/main/java/tools/jackson/databind/node/LongNode.java +++ b/src/main/java/tools/jackson/databind/node/LongNode.java @@ -62,6 +62,32 @@ public Number numberValue() { return Long.valueOf(_value); } + @Override + public short shortValue() { + if (_inShortRange()) { + return (short) _value; + } + return _reportShortCoercionRangeFail("shortValue()"); + } + + @Override + public short shortValue(short defaultValue) { + return _inShortRange() ? (short) _value : defaultValue; + } + + @Override + public short asShort() { + if (_inShortRange()) { + return (short) _value; + } + return _reportShortCoercionRangeFail("asShort()"); + } + + @Override + public short asShort(short defaultValue) { + return _inShortRange() ? (short) _value : defaultValue; + } + @Override public int intValue() { if (_inIntRange()) { diff --git a/src/main/java/tools/jackson/databind/node/NullNode.java b/src/main/java/tools/jackson/databind/node/NullNode.java index 0c9fc04a89..177e9bf7c4 100644 --- a/src/main/java/tools/jackson/databind/node/NullNode.java +++ b/src/main/java/tools/jackson/databind/node/NullNode.java @@ -65,6 +65,18 @@ protected String _asString() { /********************************************************************** */ + // `shortValue()` (etc) fine as defaults (fail); but need to override `asShort()` + + @Override + public short asShort() { + return 0; + } + + @Override + public short asShort(short defaultValue) { + return 0; + } + // `intValue()` (etc) fine as defaults (fail); but need to override `asInt()` @Override @@ -112,6 +124,18 @@ public Optional asBigIntegerOpt() { return Optional.of(asBigInteger()); } + // `floatValue()` (etc) fine as defaults (fail); but need to override `asFloat()` + + @Override + public float asFloat() { + return 0.0f; + } + + @Override + public float asFloat(float defaultValue) { + return asFloat(); + } + // `doubleValue()` (etc) fine as defaults (fail); but need to override `asDouble()` @Override diff --git a/src/main/java/tools/jackson/databind/node/NumericFPNode.java b/src/main/java/tools/jackson/databind/node/NumericFPNode.java index ac2346e737..eb532918a3 100644 --- a/src/main/java/tools/jackson/databind/node/NumericFPNode.java +++ b/src/main/java/tools/jackson/databind/node/NumericFPNode.java @@ -29,6 +29,11 @@ public abstract class NumericFPNode extends NumericNode @Override public final boolean isFloatingPointNumber() { return true; } + @Override + public final boolean canConvertToShort() { + return canConvertToExactIntegral() && _inShortRange(); + } + @Override public final boolean canConvertToInt() { return canConvertToExactIntegral() && _inIntRange(); @@ -55,6 +60,9 @@ public final boolean canConvertToExactIntegral() { @Override public final short shortValue() { if (!_inShortRange()) { + if (isNaN()) { + _reportIntCoercionNaNFail("shortValue()"); + } return _reportShortCoercionRangeFail("shortValue()"); } if (_hasFractionalPart()) { @@ -63,6 +71,33 @@ public final short shortValue() { return _asShortValueUnchecked(); } + @Override + public final short shortValue(short defaultValue) { + if (!_inShortRange() || _hasFractionalPart()) { + return defaultValue; + } + return _asShortValueUnchecked(); + } + + @Override + public short asShort() { + if (!_inShortRange()) { + if (isNaN()) { + _reportIntCoercionNaNFail("asShort()"); + } + return _reportShortCoercionRangeFail("asShort()"); + } + return _asShortValueUnchecked(); + } + + @Override + public short asShort(short defaultValue) { + if (!_inShortRange()) { + return defaultValue; + } + return _asShortValueUnchecked(); + } + @Override public final int intValue() { if (!_inIntRange()) { diff --git a/src/main/java/tools/jackson/databind/node/NumericIntNode.java b/src/main/java/tools/jackson/databind/node/NumericIntNode.java index 36eb3375fc..0b59297032 100644 --- a/src/main/java/tools/jackson/databind/node/NumericIntNode.java +++ b/src/main/java/tools/jackson/databind/node/NumericIntNode.java @@ -31,6 +31,11 @@ public abstract class NumericIntNode extends NumericNode @Override public final boolean isNaN() { return false; } + @Override final + public boolean canConvertToShort() { + return _inShortRange(); + } + @Override final public boolean canConvertToInt() { return _inIntRange(); @@ -47,13 +52,6 @@ public boolean canConvertToLong() { /********************************************************************** */ - @Override - public short shortValue() { - if (_inShortRange()) { - return (short) _asIntValueUnchecked(); - } - return _reportShortCoercionRangeFail("shortValue()"); - } // Sub-classes need to define this; but with that can implement other 5 methods @@ -85,15 +83,28 @@ public Optional asBigIntegerOpt() { return bigIntegerValueOpt(); } - // Float is simple + // Float and Double handling straight-forward for all Integral types except BigInteger + // (which needs range checks and overrides these implementations) @Override public float floatValue() { return _asFloatValueUnchecked(); } - // Double handling straight-forward for all Integral types except BigInteger - // (which needs range checks and overrides these implementations) + @Override + public float floatValue(float defaultValue) { + return _asFloatValueUnchecked(); + } + + @Override + public float asFloat() { + return _asFloatValueUnchecked(); + } + + @Override + public float asFloat(float defaultValue) { + return _asFloatValueUnchecked(); + } @Override public double doubleValue() { diff --git a/src/main/java/tools/jackson/databind/node/NumericNode.java b/src/main/java/tools/jackson/databind/node/NumericNode.java index ef01df0c05..a187e1d188 100644 --- a/src/main/java/tools/jackson/databind/node/NumericNode.java +++ b/src/main/java/tools/jackson/databind/node/NumericNode.java @@ -42,6 +42,9 @@ protected final String _valueDesc() { @Override public abstract Number numberValue(); @Override public abstract short shortValue(); + @Override public abstract short shortValue(short defaultValue); + @Override public abstract short asShort(); + @Override public abstract short asShort(short defaultValue); @Override public abstract int intValue(); @Override public abstract int intValue(int defaultValue); @@ -65,6 +68,9 @@ protected final String _valueDesc() { @Override public abstract Optional asBigIntegerOpt(); @Override public abstract float floatValue(); + @Override public abstract float floatValue(float defaultValue); + @Override public abstract float asFloat(); + @Override public abstract float asFloat(float defaultValue); @Override public abstract double doubleValue(); @Override public abstract double doubleValue(double defaultValue); @@ -80,6 +86,7 @@ protected final String _valueDesc() { @Override public abstract BigDecimal asDecimal(BigDecimal defaultValue); @Override public abstract Optional asDecimalOpt(); + @Override public abstract boolean canConvertToShort(); @Override public abstract boolean canConvertToInt(); @Override public abstract boolean canConvertToLong(); diff --git a/src/main/java/tools/jackson/databind/node/POJONode.java b/src/main/java/tools/jackson/databind/node/POJONode.java index 64a512c3e6..6deebb9624 100644 --- a/src/main/java/tools/jackson/databind/node/POJONode.java +++ b/src/main/java/tools/jackson/databind/node/POJONode.java @@ -97,7 +97,21 @@ public byte[] binaryValue() /* Overridden JsonNode methods, scalar access, numeric /********************************************************************** */ - + + // `shortValue()` (etc) fine as defaults (fail); but need to override `asShort()` + + @Override + public short asShort() { + Short S = _extractAsShort(); + return (S == null) ? super.asShort() : S; + } + + @Override + public short asShort(short defaultValue) { + Short S = _extractAsShort(); + return (S == null) ? defaultValue : S; + } + // `intValue()` (etc) fine as defaults (fail); but need to override `asInt()` @Override @@ -158,6 +172,22 @@ public Optional asBigIntegerOpt() { return (big == null) ? Optional.empty() : Optional.of(big); } + // `floatValue()` (etc) fine as defaults (fail); but need to override `asFloat()` + + @Override + public float asFloat() + { + Float f = _extractAsFloat(); + return (f == null) ? super.asFloat() : f; + } + + @Override + public float asFloat(float defaultValue) + { + Float f = _extractAsFloat(); + return (f == null) ? defaultValue : f; + } + // `doubleValue()` (etc) fine as defaults (fail); but need to override `asDouble()` @Override @@ -207,18 +237,19 @@ public Optional asDecimalOpt() { } // Consider only Integral numbers - protected Integer _extractAsInteger() { - // First, `null` same as `NullNode` - if (_value == null) { - return 0; + protected Short _extractAsShort() { + Long v = _extractAsLong(); + if (v != null && v >= Short.MIN_VALUE && v <= Short.MAX_VALUE) { + return v.shortValue(); } - // Next, coercions from integral Numbers - if (_value instanceof Number N) { - // !!! TODO: range checks - if (N instanceof Long || N instanceof Integer || N instanceof Short || N instanceof Byte - || N instanceof BigInteger) { - return N.intValue(); - } + return null; + } + + // Consider only Integral numbers + protected Integer _extractAsInteger() { + Long v = _extractAsLong(); + if (v != null && v >= Integer.MIN_VALUE && v <= Integer.MAX_VALUE) { + return v.intValue(); } return null; } @@ -231,9 +262,24 @@ protected Long _extractAsLong() { } // Next, coercions from integral Numbers if (_value instanceof Number N) { - // !!! TODO: range checks - if (N instanceof Long || N instanceof Integer || N instanceof Short || N instanceof Byte - || N instanceof BigInteger) { + // Add range check + if (N instanceof BigInteger big) { + if (big.compareTo(BI_MIN_LONG) >= 0 && big.compareTo(BI_MAX_LONG) <= 0) { + return big.longValue(); + } + } else if (N instanceof BigDecimal dec) { + if (dec.compareTo(BD_MIN_LONG) >= 0 && dec.compareTo(BD_MAX_LONG) <= 0) { + return dec.longValue(); + } + } else if (N instanceof Double D) { + if (D >= Long.MIN_VALUE && D <= Long.MAX_VALUE) { + return D.longValue(); + } + } else if (N instanceof Float F) { + if (F >= Long.MIN_VALUE && F <= Long.MAX_VALUE) { + return F.longValue(); + } + } else { return N.longValue(); } } @@ -247,25 +293,40 @@ protected BigInteger _extractAsBigInteger() { return BigInteger.ZERO; } // Next, coercions from Numbers - if (_value instanceof BigInteger big) { - return big; - } if (_value instanceof Number N) { - if (N instanceof Long || N instanceof Integer || N instanceof Short || N instanceof Byte) { + if (_value instanceof BigInteger big) { + return big; + } else if (_value instanceof BigDecimal dec) { + return dec.toBigInteger(); + } else { return BigInteger.valueOf(N.longValue()); } } return null; } + protected Float _extractAsFloat() { + if (_value instanceof Number N) { + if (_value instanceof Float F) { + return F; + } + float f = N.floatValue(); + if (Float.isFinite(f)) { + return f; + } + } + return null; + } + protected Double _extractAsDouble() { if (_value instanceof Number N) { if (_value instanceof Double D) { return D; } - // 24-Mar-2025, tatu: Should probably check for NaN from overflow - // from "too big" `BigDecimal` or `BigInteger`. But will do for now - return N.doubleValue(); + double d = N.doubleValue(); + if (Double.isFinite(d)) { + return d; + } } return null; } @@ -276,14 +337,12 @@ protected BigDecimal _extractAsBigDecimal() { return BigDecimal.ZERO; } // Next, coercions from Numbers - if (_value instanceof BigDecimal dec) { - return dec; - } - if (_value instanceof BigInteger I) { - return new BigDecimal(I); - } if (_value instanceof Number N) { - if (N instanceof Long || N instanceof Integer || N instanceof Short || N instanceof Byte) { + if (_value instanceof BigDecimal dec) { + return dec; + } else if (_value instanceof BigInteger I) { + return new BigDecimal(I); + } else if (N instanceof Long || N instanceof Integer || N instanceof Short || N instanceof Byte) { return BigDecimal.valueOf(N.longValue()); } // Use doubleValue() as a last resort for Float & Double diff --git a/src/main/java/tools/jackson/databind/node/ShortNode.java b/src/main/java/tools/jackson/databind/node/ShortNode.java index 9569751b7b..c2b28d5786 100644 --- a/src/main/java/tools/jackson/databind/node/ShortNode.java +++ b/src/main/java/tools/jackson/databind/node/ShortNode.java @@ -69,6 +69,19 @@ public Number numberValue() { @Override public short shortValue() { return _value; } + @Override + public short shortValue(short defaultValue) { return _value; } + + @Override + public short asShort() { + return _value; + } + + @Override + public short asShort(short defaultValue) { + return _value; + } + @Override public int intValue() { return _value; } diff --git a/src/main/java/tools/jackson/databind/node/StringNode.java b/src/main/java/tools/jackson/databind/node/StringNode.java index a0549d9ff9..6ac0929b79 100644 --- a/src/main/java/tools/jackson/databind/node/StringNode.java +++ b/src/main/java/tools/jackson/databind/node/StringNode.java @@ -163,6 +163,22 @@ public byte[] binaryValue() throws JacksonException { /********************************************************************** */ + @Override + public short asShort() { + Short S = _tryParseAsShort(); + if (S == null) { + return _reportCoercionFail("asShort()", Short.TYPE, + "value not a valid String representation of `short`"); + } + return S; + } + + @Override + public short asShort(short defaultValue) { + Short S = _tryParseAsShort(); + return (S == null) ? defaultValue : S; + } + @Override public int asInt() { Integer I = _tryParseAsInteger(); @@ -170,7 +186,7 @@ public int asInt() { return _reportCoercionFail("asInt()", Integer.TYPE, "value not a valid String representation of `int`"); } - return I.intValue(); + return I; } @Override @@ -192,7 +208,7 @@ public long asLong() { return _reportCoercionFail("asLong()", Long.TYPE, "value not a valid String representation of `long`"); } - return L.longValue(); + return L; } @Override @@ -231,6 +247,26 @@ public Optional asBigIntegerOpt() { return (big == null) ? Optional.empty() : Optional.of(big); } + // `floatValue()` (etc) fine as defaults (fail); but need to override `asFloat()` + + @Override + public float asFloat() + { + Float F = _tryParseAsFloat(); + if (F == null) { + return _reportCoercionFail("asFloat()", Float.TYPE, + "value not a valid String representation of `float`"); + } + return F; + } + + @Override + public float asFloat(float defaultValue) + { + Float F = _tryParseAsFloat(); + return (F == null) ? defaultValue : F; + } + // `doubleValue()` (etc) fine as defaults (fail); but need to override `asDouble()` @Override @@ -241,7 +277,7 @@ public double asDouble() return _reportCoercionFail("asDouble()", Double.TYPE, "value not a valid String representation of `double`"); } - return (d == null) ? super.asDouble() : d; + return d; } @Override @@ -281,6 +317,14 @@ public Optional asDecimalOpt() { return (dec == null) ? Optional.empty() : Optional.of(dec); } + protected Short _tryParseAsShort() { + Integer I = _tryParseAsInteger(); + if (I != null && I >= Short.MIN_VALUE && I <= Short.MAX_VALUE) { + return I.shortValue(); + } + return null; + } + protected Integer _tryParseAsInteger() { if (NumberInput.looksLikeValidNumber(_value)) { try { @@ -315,6 +359,17 @@ protected BigInteger _tryParseAsBigInteger() { return null; } + protected Float _tryParseAsFloat() { + if (NumberInput.looksLikeValidNumber(_value)) { + try { + return NumberInput.parseFloat(_value, true); + } catch (NumberFormatException e) { + ; + } + } + return null; + } + protected Double _tryParseAsDouble() { if (NumberInput.looksLikeValidNumber(_value)) { try { diff --git a/src/main/java/tools/jackson/databind/node/ValueNode.java b/src/main/java/tools/jackson/databind/node/ValueNode.java index 336381d366..4e30396ac4 100644 --- a/src/main/java/tools/jackson/databind/node/ValueNode.java +++ b/src/main/java/tools/jackson/databind/node/ValueNode.java @@ -1,5 +1,7 @@ package tools.jackson.databind.node; +import java.math.BigDecimal; +import java.math.BigInteger; import java.util.List; import tools.jackson.core.*; @@ -18,6 +20,21 @@ public abstract class ValueNode { private static final long serialVersionUID = 3L; + // For numeric range checks + protected final static BigInteger BI_MIN_SHORT = BigInteger.valueOf(Short.MIN_VALUE); + protected final static BigInteger BI_MAX_SHORT = BigInteger.valueOf(Short.MAX_VALUE); + protected final static BigInteger BI_MIN_INTEGER = BigInteger.valueOf(Integer.MIN_VALUE); + protected final static BigInteger BI_MAX_INTEGER = BigInteger.valueOf(Integer.MAX_VALUE); + protected final static BigInteger BI_MIN_LONG = BigInteger.valueOf(Long.MIN_VALUE); + protected final static BigInteger BI_MAX_LONG = BigInteger.valueOf(Long.MAX_VALUE); + + protected final static BigDecimal BD_MIN_SHORT = BigDecimal.valueOf(Short.MIN_VALUE); + protected final static BigDecimal BD_MAX_SHORT = BigDecimal.valueOf(Short.MAX_VALUE); + protected final static BigDecimal BD_MIN_INTEGER = BigDecimal.valueOf(Integer.MIN_VALUE); + protected final static BigDecimal BD_MAX_INTEGER = BigDecimal.valueOf(Integer.MAX_VALUE); + protected final static BigDecimal BD_MIN_LONG = BigDecimal.valueOf(Long.MIN_VALUE); + protected final static BigDecimal BD_MAX_LONG = BigDecimal.valueOf(Long.MAX_VALUE); + protected final static JsonNode MISSING = MissingNode.getInstance(); protected ValueNode() { } diff --git a/src/test/java/tools/jackson/databind/node/JsonNodeDoubleValueTest.java b/src/test/java/tools/jackson/databind/node/JsonNodeDoubleValueTest.java index b50f630efa..b7be1bcfe4 100644 --- a/src/test/java/tools/jackson/databind/node/JsonNodeDoubleValueTest.java +++ b/src/test/java/tools/jackson/databind/node/JsonNodeDoubleValueTest.java @@ -108,6 +108,7 @@ public void failDoubleValueFromNonNumberScalar() _assertDoubleValueFailForNonNumber(NODES.stringNode("123")); _assertDoubleValueFailForNonNumber(NODES.rawValueNode(new RawValue("abc"))); _assertDoubleValueFailForNonNumber(NODES.pojoNode(Boolean.TRUE)); + _assertDoubleValueFailForNonNumber(NODES.pojoNode(3.8d)); } @Test @@ -213,10 +214,12 @@ public void asDoubleFromNonNumberScalar() _assertAsDoubleFailForNonNumber(NODES.pojoNode(Boolean.TRUE)); _assertAsDoubleFailForNonNumber(NODES.stringNode("abc"), "not a valid String representation of `double`"); + _assertAsDoubleFailForNonNumber(NODES.pojoNode(new BigDecimal(BigInteger.TEN.pow(310)))); // Then passing ones: - _assertAsDouble(2.5d, NODES.pojoNode(2.5d)); _assertAsDouble(0.5d, NODES.stringNode("0.5")); + _assertAsDouble(2.5d, NODES.pojoNode(2.5d)); + _assertAsDouble(1e40, NODES.pojoNode(1e40)); } @Test diff --git a/src/test/java/tools/jackson/databind/node/JsonNodeFloatValueTest.java b/src/test/java/tools/jackson/databind/node/JsonNodeFloatValueTest.java index 3be255f7e2..1f12a5dfc5 100644 --- a/src/test/java/tools/jackson/databind/node/JsonNodeFloatValueTest.java +++ b/src/test/java/tools/jackson/databind/node/JsonNodeFloatValueTest.java @@ -2,6 +2,7 @@ import java.math.BigDecimal; import java.math.BigInteger; +import java.util.OptionalDouble; import org.junit.jupiter.api.Test; @@ -26,28 +27,28 @@ public class JsonNodeFloatValueTest @Test public void floatValueFromNumberIntOk() { - final float ONE_D = 1.0f; + final float ONE_F = 1.0f; // Then other integer types - assertEquals(ONE_D, NODES.numberNode((byte) 1).floatValue()); - assertEquals((float)Byte.MIN_VALUE, NODES.numberNode(Byte.MIN_VALUE).floatValue()); - assertEquals((float)Byte.MAX_VALUE, NODES.numberNode(Byte.MAX_VALUE).floatValue()); + _assertFloatValue(ONE_F, NODES.numberNode((byte) 1)); + _assertFloatValue((float)Byte.MIN_VALUE, NODES.numberNode(Byte.MIN_VALUE)); + _assertFloatValue((float)Byte.MAX_VALUE, NODES.numberNode(Byte.MAX_VALUE)); - assertEquals(ONE_D, NODES.numberNode((short) 1).floatValue()); - assertEquals((float)Short.MIN_VALUE, NODES.numberNode(Short.MIN_VALUE).floatValue()); - assertEquals((float)Short.MAX_VALUE, NODES.numberNode(Short.MAX_VALUE).floatValue()); + _assertFloatValue(ONE_F, NODES.numberNode((short) 1)); + _assertFloatValue((float)Short.MIN_VALUE, NODES.numberNode(Short.MIN_VALUE)); + _assertFloatValue((float)Short.MAX_VALUE, NODES.numberNode(Short.MAX_VALUE)); - assertEquals(ONE_D, NODES.numberNode(1).floatValue()); - assertEquals((float) Integer.MIN_VALUE, NODES.numberNode(Integer.MIN_VALUE).floatValue()); - assertEquals((float) Integer.MAX_VALUE, NODES.numberNode(Integer.MAX_VALUE).floatValue()); - - assertEquals(ONE_D, NODES.numberNode(1L).floatValue()); - assertEquals((float) Long.MIN_VALUE, NODES.numberNode(Long.MIN_VALUE).floatValue()); - assertEquals((float) Long.MAX_VALUE, NODES.numberNode(Long.MAX_VALUE).floatValue()); + _assertFloatValue(ONE_F, NODES.numberNode(1)); + _assertFloatValue((float) Integer.MIN_VALUE, NODES.numberNode(Integer.MIN_VALUE)); + _assertFloatValue((float) Integer.MAX_VALUE, NODES.numberNode(Integer.MAX_VALUE)); + + _assertFloatValue(ONE_F, NODES.numberNode(1L)); + _assertFloatValue((float) Long.MIN_VALUE, NODES.numberNode(Long.MIN_VALUE)); + _assertFloatValue((float) Long.MAX_VALUE, NODES.numberNode(Long.MAX_VALUE)); - assertEquals(ONE_D, NODES.numberNode(BigInteger.valueOf(1)).floatValue()); - assertEquals((float) Long.MIN_VALUE, NODES.numberNode(BigInteger.valueOf(Long.MIN_VALUE)).floatValue()); - assertEquals((float) Long.MAX_VALUE, NODES.numberNode(BigInteger.valueOf(Long.MAX_VALUE)).floatValue()); + _assertFloatValue(ONE_F, NODES.numberNode(BigInteger.valueOf(1))); + _assertFloatValue((float) Long.MIN_VALUE, NODES.numberNode(BigInteger.valueOf(Long.MIN_VALUE))); + _assertFloatValue((float) Long.MAX_VALUE, NODES.numberNode(BigInteger.valueOf(Long.MAX_VALUE))); } @Test @@ -67,20 +68,20 @@ public void failfloatValueFromNumberIntRange() { @Test public void floatValueFromNumberFPOk() { - assertEquals(1.0f, NODES.numberNode(1.0f).floatValue()); - assertEquals(100_000.25f, NODES.numberNode(100_000.25f).floatValue()); - assertEquals(-100_000.25f, NODES.numberNode(-100_000.25f).floatValue()); + _assertFloatValue(1.0f, NODES.numberNode(1.0f)); + _assertFloatValue(100_000.25f, NODES.numberNode(100_000.25f)); + _assertFloatValue(-100_000.25f, NODES.numberNode(-100_000.25f)); - assertEquals(1.0f, NODES.numberNode(1.0d).floatValue()); - assertEquals(100_000.25f, NODES.numberNode(100_000.25d).floatValue()); - assertEquals(-100_000.25f, NODES.numberNode(-100_000.25d).floatValue()); + _assertFloatValue(1.0f, NODES.numberNode(1.0d)); + _assertFloatValue(100_000.25f, NODES.numberNode(100_000.25d)); + _assertFloatValue(-100_000.25f, NODES.numberNode(-100_000.25d)); - assertEquals(1.25f, - NODES.numberNode(BigDecimal.valueOf(1.25d)).floatValue()); - assertEquals((float) Long.MIN_VALUE, - NODES.numberNode(BigDecimal.valueOf((double) Long.MIN_VALUE)).floatValue()); - assertEquals((float) Long.MAX_VALUE, - NODES.numberNode(BigDecimal.valueOf((double) Long.MAX_VALUE)).floatValue()); + _assertFloatValue(1.25f, + NODES.numberNode(BigDecimal.valueOf(1.25d))); + _assertFloatValue((float) Long.MIN_VALUE, + NODES.numberNode(BigDecimal.valueOf((double) Long.MIN_VALUE))); + _assertFloatValue((float) Long.MAX_VALUE, + NODES.numberNode(BigDecimal.valueOf((double) Long.MAX_VALUE))); } @Test @@ -111,6 +112,7 @@ public void failFloatValueFromNonNumberScalar() _assertFailFloatForNonNumber(NODES.stringNode("123")); _assertFailFloatForNonNumber(NODES.rawValueNode(new RawValue("abc"))); _assertFailFloatForNonNumber(NODES.pojoNode(Boolean.TRUE)); + _assertFailFloatForNonNumber(NODES.pojoNode(3.8f)); } @Test @@ -127,8 +129,127 @@ public void failFloatValueFromMiscOther() _assertFailFloatForNonNumber(NODES.missingNode()); } + // // // asFloat() + + // from Integers + + @Test + public void asFloatFromNumberIntOk() + { + final float ONE_F = (float) 1; + + _assertAsFloat(ONE_F, NODES.numberNode((byte) 1)); + _assertAsFloat((float)Byte.MIN_VALUE, NODES.numberNode(Byte.MIN_VALUE)); + _assertAsFloat((float)Byte.MAX_VALUE, NODES.numberNode(Byte.MAX_VALUE)); + + _assertAsFloat(ONE_F, NODES.numberNode((short) 1)); + _assertAsFloat((float)Short.MIN_VALUE, NODES.numberNode(Short.MIN_VALUE)); + _assertAsFloat((float)Short.MAX_VALUE, NODES.numberNode(Short.MAX_VALUE)); + + _assertAsFloat(ONE_F, NODES.numberNode(1)); + _assertAsFloat((float) Integer.MIN_VALUE, NODES.numberNode(Integer.MIN_VALUE)); + _assertAsFloat((float) Integer.MAX_VALUE, NODES.numberNode(Integer.MAX_VALUE)); + + _assertAsFloat(ONE_F, NODES.numberNode(1L)); + _assertAsFloat((float) Long.MIN_VALUE, NODES.numberNode(Long.MIN_VALUE)); + _assertAsFloat((float) Long.MAX_VALUE, NODES.numberNode(Long.MAX_VALUE)); + + _assertAsFloat(ONE_F, NODES.numberNode(BigInteger.valueOf(1))); + _assertAsFloat((float) Long.MIN_VALUE, NODES.numberNode(BigInteger.valueOf(Long.MIN_VALUE))); + _assertAsFloat((float) Long.MAX_VALUE, NODES.numberNode(BigInteger.valueOf(Long.MAX_VALUE))); + } + + @Test + public void asFloatFailFromNumberIntRange() { + // Can only fail for underflow/overflow: and that only for / BigInteger + // (neither Integer nor Long is outside of range of even Float). + + final BigInteger tooBig = BigInteger.TEN.pow(310); + final BigInteger tooSmall = tooBig.negate(); + + _assertAsFloatFailForValueRange(NODES.numberNode(tooBig)); + _assertAsFloatFailForValueRange(NODES.numberNode(tooSmall)); + } + + // Numbers/FPs + + @Test + public void asFloatFromNumberFPOk() + { + _assertAsFloat(1.0f, NODES.numberNode(1.0f)); + _assertAsFloat(100_000.0f, NODES.numberNode(100_000.0f)); + _assertAsFloat(-100_000.0f, NODES.numberNode(-100_000.0f)); + + _assertAsFloat(1.0f, NODES.numberNode(1.0d)); + _assertAsFloat(100_000.0f, NODES.numberNode(100_000.0d)); + _assertAsFloat(-100_000.0f, NODES.numberNode(-100_000.0d)); + + _assertAsFloat(1.0f, + NODES.numberNode(BigDecimal.valueOf(1.0d))); + _assertAsFloat((float) Long.MIN_VALUE, + NODES.numberNode(BigDecimal.valueOf((float) Long.MIN_VALUE))); + _assertAsFloat((float) Long.MAX_VALUE, + NODES.numberNode(BigDecimal.valueOf((float) Long.MAX_VALUE))); + } + + @Test + public void asFloatFromNumberFPRangeFail() + { + // Can only fail from BigDecimal (similar to ints vs BigInteger) + + final BigDecimal tooBig = new BigDecimal(BigInteger.TEN.pow(310)) + .add(BigDecimal.valueOf(0.125)); + final BigDecimal tooSmall = tooBig.negate(); + + _assertAsFloatFailForValueRange(NODES.numberNode(tooBig)); + _assertAsFloatFailForValueRange(NODES.numberNode(tooSmall)); + } + + // from non-Numeric types + + @Test + public void asFloatFromNonNumberScalar() + { + // First, failing cases: + + _assertAsFloatFailForNonNumber(NODES.booleanNode(true)); + _assertAsFloatFailForNonNumber(NODES.binaryNode(new byte[3])); + _assertAsFloatFailForNonNumber(NODES.rawValueNode(new RawValue("abc"))); + _assertAsFloatFailForNonNumber(NODES.pojoNode(Boolean.TRUE)); + _assertAsFloatFailForNonNumber(NODES.stringNode("abc"), + "not a valid String representation of `float`"); + _assertAsFloatFailForNonNumber(NODES.pojoNode(1e40)); + + // Then passing ones: + _assertAsFloat(2.5f, NODES.pojoNode(2.5f)); + _assertAsFloat(0.5f, NODES.stringNode("0.5")); + } + + @Test + public void asFloatFromStructuralFail() + { + _assertAsFloatFailForNonNumber(NODES.arrayNode(3)); + _assertAsFloatFailForNonNumber(NODES.objectNode()); + } + + @Test + public void asFloatFromMiscOther() + { + // Null node converts to 0.0f; missing fails + _assertAsFloat((float) 0, NODES.nullNode()); + _assertAsFloatFailForNonNumber(NODES.missingNode()); + } + + // // // Shared helper methods + private void _assertFloatValue(float expected, JsonNode node) { + assertEquals(expected, node.floatValue()); + + // and defaults + assertEquals(expected, node.floatValue(-9999.5f)); + } + private void _assertFailFloatForValueRange(JsonNode node) { Exception e = assertThrows(JsonNodeException.class, () -> node.floatValue(), @@ -136,6 +257,8 @@ private void _assertFailFloatForValueRange(JsonNode node) { assertThat(e.getMessage()) .contains("cannot convert value") .contains("value not in 32-bit `float` range"); + + assertEquals(-2.25f, node.floatValue(-2.25f)); } private void _assertFailFloatForNonNumber(JsonNode node) { @@ -145,5 +268,43 @@ private void _assertFailFloatForNonNumber(JsonNode node) { assertThat(e.getMessage()) .contains("cannot convert value") .contains("value type not numeric"); + + assertEquals(-2.25f, node.floatValue(-2.25f)); } + + private void _assertAsFloat(float expected, JsonNode node) { + assertEquals(expected, node.asFloat()); + + // and defaults + assertEquals(expected, node.asFloat(-9999.5f)); + } + + private void _assertAsFloatFailForValueRange(JsonNode node) { + Exception e = assertThrows(JsonNodeException.class, + () -> node.asFloat(), + "For ("+node.getClass().getSimpleName()+") value: "+node); + assertThat(e.getMessage()) + .contains("asFloat()") + .contains("cannot convert value") + .contains("value not in 32-bit `float` range"); + + assertEquals(-2.25f, node.asFloat(-2.25f)); + } + + private void _assertAsFloatFailForNonNumber(JsonNode node) { + _assertAsFloatFailForNonNumber(node, "value type not numeric"); + } + + private void _assertAsFloatFailForNonNumber(JsonNode node, String extraMatch) { + Exception e = assertThrows(JsonNodeException.class, + () -> node.asFloat(), + "For ("+node.getClass().getSimpleName()+") value: "+node); + assertThat(e.getMessage()) + .contains("asFloat()") + .contains("cannot convert value") + .contains(extraMatch); + + assertEquals(1.5f, node.asFloat(1.5f)); + } + } diff --git a/src/test/java/tools/jackson/databind/node/JsonNodeIntValueTest.java b/src/test/java/tools/jackson/databind/node/JsonNodeIntValueTest.java index 70de8afa4e..3d30e11dec 100644 --- a/src/test/java/tools/jackson/databind/node/JsonNodeIntValueTest.java +++ b/src/test/java/tools/jackson/databind/node/JsonNodeIntValueTest.java @@ -143,6 +143,7 @@ public void intValueFromNonNumberScalarFail() _assertIntValueFailForNonNumber(NODES.stringNode("123")); _assertIntValueFailForNonNumber(NODES.rawValueNode(new RawValue("abc"))); _assertIntValueFailForNonNumber(NODES.pojoNode(Boolean.TRUE)); + _assertIntValueFailForNonNumber(NODES.pojoNode(456)); } @Test @@ -279,14 +280,14 @@ public void asIntFromNonNumberScalar() _assertAsIntFailForNonNumber(NODES.binaryNode(new byte[3])); _assertAsIntFailForNonNumber(NODES.rawValueNode(new RawValue("abc"))); _assertAsIntFailForNonNumber(NODES.pojoNode(Boolean.TRUE)); - _assertAsIntFailForNonNumber(NODES.stringNode("abc"), "value not a valid String representation of `int`"); + _assertAsIntFailForNonNumber(NODES.pojoNode(123_456_789_000L)); // Some pass: - - _assertAsInt(456, NODES.pojoNode(456)); _assertAsInt(123, NODES.stringNode("123")); + _assertAsInt(456, NODES.pojoNode(456L)); + _assertAsInt(789, NODES.pojoNode(BigInteger.valueOf(789))); } @Test diff --git a/src/test/java/tools/jackson/databind/node/JsonNodeLongValueTest.java b/src/test/java/tools/jackson/databind/node/JsonNodeLongValueTest.java index 0ceb308497..3df60fe1f0 100644 --- a/src/test/java/tools/jackson/databind/node/JsonNodeLongValueTest.java +++ b/src/test/java/tools/jackson/databind/node/JsonNodeLongValueTest.java @@ -140,6 +140,7 @@ public void longValueFromNonNumberScalarFail() _assertLongValueFailForNonNumber(NODES.stringNode("123")); _assertLongValueFailForNonNumber(NODES.rawValueNode(new RawValue("abc"))); _assertLongValueFailForNonNumber(NODES.pojoNode(Boolean.TRUE)); + _assertLongValueFailForNonNumber(NODES.pojoNode(456L)); } @Test @@ -276,10 +277,12 @@ public void asLongFromNonNumberScalarFail() _assertAsLongFailForNonNumber(NODES.rawValueNode(new RawValue("abc"))); _assertAsLongFailForNonNumber(NODES.pojoNode(Boolean.TRUE)); _assertAsLongFailForNonNumber(NODES.stringNode("abcdef"), "not a valid String representation of `long`"); + _assertAsLongFailForNonNumber(NODES.pojoNode(1e40)); // Some pass - _assertAsLong(123456L, NODES.pojoNode(123456L)); _assertAsLong(1234L, NODES.stringNode("1234")); + _assertAsLong(123456L, NODES.pojoNode(123456L)); + _assertAsLong(789L, NODES.pojoNode(BigInteger.valueOf(789))); } @Test diff --git a/src/test/java/tools/jackson/databind/node/JsonNodeShortValueTest.java b/src/test/java/tools/jackson/databind/node/JsonNodeShortValueTest.java index 93ac0fc4be..816be439ab 100644 --- a/src/test/java/tools/jackson/databind/node/JsonNodeShortValueTest.java +++ b/src/test/java/tools/jackson/databind/node/JsonNodeShortValueTest.java @@ -34,27 +34,27 @@ public void shortValueFromNumberIntOk() final short MAX_SHORT = Short.MAX_VALUE; // First safe from `short` - assertEquals(SHORT_1, NODES.numberNode((short) 1).shortValue()); - assertEquals((int)Short.MIN_VALUE, NODES.numberNode(MIN_SHORT).shortValue()); - assertEquals((int)Short.MAX_VALUE, NODES.numberNode(MAX_SHORT).shortValue()); + _assertShortValue(SHORT_1, NODES.numberNode((short) 1)); + _assertShortValue(Short.MIN_VALUE, NODES.numberNode(MIN_SHORT)); + _assertShortValue(Short.MAX_VALUE, NODES.numberNode(MAX_SHORT)); // Then other integer types - assertEquals(SHORT_1, NODES.numberNode((byte) 1).shortValue()); - assertEquals((short) Byte.MIN_VALUE, NODES.numberNode(Byte.MIN_VALUE).shortValue()); - assertEquals((short) Byte.MAX_VALUE, NODES.numberNode(Byte.MAX_VALUE).shortValue()); + _assertShortValue(SHORT_1, NODES.numberNode((byte) 1)); + _assertShortValue(Byte.MIN_VALUE, NODES.numberNode(Byte.MIN_VALUE)); + _assertShortValue(Byte.MAX_VALUE, NODES.numberNode(Byte.MAX_VALUE)); - assertEquals(SHORT_1, NODES.numberNode(1).shortValue()); - assertEquals(MIN_SHORT, NODES.numberNode((int) MIN_SHORT).shortValue()); - assertEquals(MAX_SHORT, NODES.numberNode((int) MAX_SHORT).shortValue()); + _assertShortValue(SHORT_1, NODES.numberNode(1)); + _assertShortValue(MIN_SHORT, NODES.numberNode((int) MIN_SHORT)); + _assertShortValue(MAX_SHORT, NODES.numberNode((int) MAX_SHORT)); - assertEquals(SHORT_1, NODES.numberNode(1L).shortValue()); - assertEquals(MIN_SHORT, NODES.numberNode((long) MIN_SHORT).shortValue()); - assertEquals(MAX_SHORT, NODES.numberNode((long) MAX_SHORT).shortValue()); + _assertShortValue(SHORT_1, NODES.numberNode(1L)); + _assertShortValue(MIN_SHORT, NODES.numberNode((long) MIN_SHORT)); + _assertShortValue(MAX_SHORT, NODES.numberNode((long) MAX_SHORT)); - assertEquals(SHORT_1, NODES.numberNode(BigInteger.valueOf(1)).shortValue()); - assertEquals(MIN_SHORT, NODES.numberNode(BigInteger.valueOf(MIN_SHORT)).shortValue()); - assertEquals(MAX_SHORT, NODES.numberNode(BigInteger.valueOf(MAX_SHORT)).shortValue()); + _assertShortValue(SHORT_1, NODES.numberNode(BigInteger.valueOf(1))); + _assertShortValue(MIN_SHORT, NODES.numberNode(BigInteger.valueOf(MIN_SHORT))); + _assertShortValue(MAX_SHORT, NODES.numberNode(BigInteger.valueOf(MAX_SHORT))); } @Test @@ -73,22 +73,22 @@ public void shortValueFromNumberIntFailRange() { @Test public void shortValueFromNumberFPOk() { - assertEquals(1, NODES.numberNode(1.0f).shortValue()); - assertEquals(10_000, NODES.numberNode(10_000.0f).shortValue()); - assertEquals(-10_000, NODES.numberNode(-10_000.0f).shortValue()); - - assertEquals(1, NODES.numberNode(1.0d).shortValue()); - assertEquals(10_000, NODES.numberNode(10_000.0d).shortValue()); - assertEquals(-10_000, NODES.numberNode(-10_000.0d).shortValue()); - assertEquals(Short.MIN_VALUE, NODES.numberNode((double) Short.MIN_VALUE).shortValue()); - assertEquals(Short.MAX_VALUE, NODES.numberNode((double) Short.MAX_VALUE).shortValue()); - - assertEquals(1, - NODES.numberNode(BigDecimal.valueOf(1.0d)).shortValue()); - assertEquals(Short.MIN_VALUE, - NODES.numberNode(BigDecimal.valueOf((double) Short.MIN_VALUE)).shortValue()); - assertEquals(Short.MAX_VALUE, - NODES.numberNode(BigDecimal.valueOf((double) Short.MAX_VALUE)).shortValue()); + _assertShortValue((short) 1, NODES.numberNode(1.0f)); + _assertShortValue((short) 10_000, NODES.numberNode(10_000.0f)); + _assertShortValue((short) -10_000, NODES.numberNode(-10_000.0f)); + + _assertShortValue((short) 1, NODES.numberNode(1.0d)); + _assertShortValue((short) 10_000, NODES.numberNode(10_000.0d)); + _assertShortValue((short) -10_000, NODES.numberNode(-10_000.0d)); + _assertShortValue(Short.MIN_VALUE, NODES.numberNode((double) Short.MIN_VALUE)); + _assertShortValue(Short.MAX_VALUE, NODES.numberNode((double) Short.MAX_VALUE)); + + _assertShortValue((short) 1, + NODES.numberNode(BigDecimal.valueOf(1.0d))); + _assertShortValue(Short.MIN_VALUE, + NODES.numberNode(BigDecimal.valueOf((double) Short.MIN_VALUE))); + _assertShortValue(Short.MAX_VALUE, + NODES.numberNode(BigDecimal.valueOf((double) Short.MAX_VALUE))); } @Test @@ -137,6 +137,163 @@ public void shortValueFromNonNumberFail() _assertFailShortForNonNumber(NODES.nullNode()); _assertFailShortForNonNumber(NODES.missingNode()); + _assertFailShortForNonNumber(NODES.pojoNode((short) 456)); + } + + // // // asShort() + + // Numbers/Integers + + @Test + public void asShortFromNumberIntOk() + { + // First safe from `short` + _assertAsShort((short) 1, NODES.numberNode((short) 1)); + _assertAsShort(Short.MIN_VALUE, NODES.numberNode(Short.MIN_VALUE)); + _assertAsShort(Short.MAX_VALUE, NODES.numberNode(Short.MAX_VALUE)); + + // Then other integer types + _assertAsShort((short) 1, NODES.numberNode((byte) 1)); + _assertAsShort(Byte.MIN_VALUE, NODES.numberNode(Byte.MIN_VALUE)); + _assertAsShort(Byte.MAX_VALUE, NODES.numberNode(Byte.MAX_VALUE)); + + _assertAsShort((short) 1, NODES.numberNode(1)); + _assertAsShort(Short.MIN_VALUE, NODES.numberNode((int) Short.MIN_VALUE)); + _assertAsShort(Short.MAX_VALUE, NODES.numberNode((int) Short.MAX_VALUE)); + + _assertAsShort((short) 1, NODES.numberNode(1L)); + _assertAsShort(Short.MIN_VALUE, NODES.numberNode((long) Short.MIN_VALUE)); + _assertAsShort(Short.MAX_VALUE, NODES.numberNode((long) Short.MAX_VALUE)); + + _assertAsShort((short) 1, NODES.numberNode(BigInteger.valueOf(1))); + _assertAsShort(Short.MIN_VALUE, NODES.numberNode(BigInteger.valueOf(Short.MIN_VALUE))); + _assertAsShort(Short.MAX_VALUE, NODES.numberNode(BigInteger.valueOf(Short.MAX_VALUE))); + } + + @Test + public void asShortFromNumberIntFailRange() { + // Can only fail for underflow/overflow: and that only for Integer / Long / BigInteger + final int underflow = -1 + Short.MIN_VALUE; + final long overflow = +1L + Short.MAX_VALUE; + + _assertAsShortFailForValueRange(NODES.numberNode(underflow)); + _assertAsShortFailForValueRange(NODES.numberNode(overflow)); + + _assertAsShortFailForValueRange(NODES.numberNode(BigInteger.valueOf(underflow))); + _assertAsShortFailForValueRange(NODES.numberNode(BigInteger.valueOf(overflow))); + } + + // Numbers/FPs + + @Test + public void asShortFromNumberFPOk() + { + _assertAsShort((short) 1, NODES.numberNode(1.0f)); + _assertAsShort((short) 100, NODES.numberNode(100.0f)); + _assertAsShort((short) -100, NODES.numberNode(-100.0f)); + + _assertAsShort((short) 1, NODES.numberNode(1.0d)); + _assertAsShort((short) 100, NODES.numberNode(100.0d)); + _assertAsShort((short) -100, NODES.numberNode(-100.0d)); + _assertAsShort(Short.MIN_VALUE, NODES.numberNode((double) Short.MIN_VALUE)); + _assertAsShort(Short.MAX_VALUE, NODES.numberNode((double) Short.MAX_VALUE)); + + _assertAsShort((short) 1, + NODES.numberNode(BigDecimal.valueOf(1.0d))); + _assertAsShort(Short.MIN_VALUE, + NODES.numberNode(BigDecimal.valueOf((double) Short.MIN_VALUE))); + _assertAsShort(Short.MAX_VALUE, + NODES.numberNode(BigDecimal.valueOf((double) Short.MAX_VALUE))); + } + + @Test + public void asShortFromNumberFPFailRange() + { + // Can only fail for underflow/overflow: and that only for Long / BigInteger + final long underflow = Short.MIN_VALUE - 1L; + final long overflow = Short.MAX_VALUE + 1L; + + _assertAsShortFailForValueRange(NODES.numberNode((float)underflow)); + _assertAsShortFailForValueRange(NODES.numberNode((double)overflow)); + + // Float is too inexact for using same test as Double, so: + + _assertAsShortFailForValueRange(NODES.numberNode(-Float.MAX_VALUE)); + _assertAsShortFailForValueRange(NODES.numberNode(Float.MAX_VALUE)); + + _assertAsShortFailForValueRange(NODES.numberNode(BigDecimal.valueOf(underflow))); + _assertAsShortFailForValueRange(NODES.numberNode(BigDecimal.valueOf(overflow))); + } + + @Test + public void asShortFromNumberFPWithFraction() + { + _assertAsShort((short) 100, NODES.numberNode(100.75f)); + _assertAsShort((short) -1, NODES.numberNode(-1.25f)); + + _assertAsShort((short) 100, NODES.numberNode(100.75d)); + _assertAsShort((short) -1, NODES.numberNode(-1.25d)); + + _assertAsShort((short) 100, NODES.numberNode(BigDecimal.valueOf(100.75d))); + _assertAsShort((short) -1, NODES.numberNode(BigDecimal.valueOf(-1.25d))); + } + + @Test + public void asIntFromNumberFPFailNaN() + { + _assertAsShortFailForNaN(NODES.numberNode(Float.NaN)); + _assertAsShortFailForNaN(NODES.numberNode(Float.NEGATIVE_INFINITY)); + _assertAsShortFailForNaN(NODES.numberNode(Float.POSITIVE_INFINITY)); + + _assertAsShortFailForNaN(NODES.numberNode(Double.NaN)); + _assertAsShortFailForNaN(NODES.numberNode(Double.NEGATIVE_INFINITY)); + _assertAsShortFailForNaN(NODES.numberNode(Double.POSITIVE_INFINITY)); + } + + // non-Numeric types + + @Test + public void asShortFromNonNumberScalar() + { + // Some fail: + _assertAsShortFailForNonNumber(NODES.booleanNode(true)); + _assertAsShortFailForNonNumber(NODES.binaryNode(new byte[3])); + _assertAsShortFailForNonNumber(NODES.rawValueNode(new RawValue("abc"))); + _assertAsShortFailForNonNumber(NODES.pojoNode(Boolean.TRUE)); + _assertAsShortFailForNonNumber(NODES.stringNode("abc"), + "value not a valid String representation of `short`"); + _assertAsShortFailForNonNumber(NODES.pojoNode(123_456)); + + // Some pass: + _assertAsShort((short) 123, NODES.stringNode("123")); + _assertAsShort((short) 456, NODES.pojoNode(456)); + _assertAsShort((short) 789, NODES.pojoNode(BigInteger.valueOf(789))); + } + + @Test + public void asIntFromStructuralFail() + { + _assertAsShortFailForNonNumber(NODES.arrayNode(3)); + _assertAsShortFailForNonNumber(NODES.objectNode()); + } + + @Test + public void asIntFromMiscOther() + { + // NullNode -> 0 but "missing" still fails + _assertAsShort((short) 0, NODES.nullNode()); + + _assertAsShortFailForNonNumber(NODES.missingNode()); + } + + + // // // Shared helper methods: shortValue() + + private void _assertShortValue(short expected, JsonNode node) { + assertEquals(expected, node.shortValue()); + + // and defaulting + assertEquals(expected, node.shortValue((short) 99)); } // // // Shared helper methods @@ -149,6 +306,8 @@ private void _assertFailShortForValueRange(JsonNode node) { .contains("cannot convert value") .contains("value not in 16-bit `short` range"); + // assert defaulting + assertEquals(99, node.shortValue((short) 99)); } private void _assertFailShortValueForFraction(JsonNode node) { @@ -158,6 +317,9 @@ private void _assertFailShortValueForFraction(JsonNode node) { assertThat(e.getMessage()) .contains("cannot convert value") .contains("to `short`: value has fractional part"); + + // assert defaulting + assertEquals(99, node.shortValue((short) 99)); } private void _assertFailShortForNonNumber(JsonNode node) { @@ -167,5 +329,62 @@ private void _assertFailShortForNonNumber(JsonNode node) { assertThat(e.getMessage()) .contains("cannot convert value") .contains("value type not numeric"); + + // assert defaulting + assertEquals(99, node.shortValue((short) 99)); } + + // // // Shared helper methods: asShort() + + private void _assertAsShort(short expected, JsonNode node) { + assertEquals(expected, node.asShort()); + + // and defaulting + assertEquals(expected, node.asShort((short) 99)); + } + + private void _assertAsShortFailForValueRange(JsonNode node) { + Exception e = assertThrows(JsonNodeException.class, + () -> node.asShort(), + "For ("+node.getClass().getSimpleName()+") value: "+node); + assertThat(e.getMessage()) + .contains("asShort()") + .contains("cannot convert value") + .contains("value not in 16-bit `short` range"); + + // assert defaulting + assertEquals(99, node.asShort((short) 99)); + } + + private void _assertAsShortFailForNonNumber(JsonNode node) { + _assertAsShortFailForNonNumber(node, "value type not numeric"); + } + + private void _assertAsShortFailForNonNumber(JsonNode node, String extraFailMsg) { + Exception e = assertThrows(JsonNodeException.class, + () -> node.asShort(), + "For ("+node.getClass().getSimpleName()+") value: "+node); + assertThat(e.getMessage()) + .contains("asShort()") + .contains("cannot convert value") + .contains(extraFailMsg); + + // assert defaulting + assertEquals(99, node.asShort((short) 99)); + } + + private void _assertAsShortFailForNaN(JsonNode node) { + Exception e = assertThrows(JsonNodeException.class, + () -> node.asShort(), + "For ("+node.getClass().getSimpleName()+") value: "+node); + assertThat(e.getMessage()) + .contains("asShort()") + .contains("cannot convert value") + .contains("value non-Finite"); + + // Verify default value handling + assertEquals(99, node.asShort((short) 99)); + } + + } diff --git a/src/test/java/tools/jackson/databind/node/POJONodeTest.java b/src/test/java/tools/jackson/databind/node/POJONodeTest.java index 6fe01159e6..fe0b6784bf 100644 --- a/src/test/java/tools/jackson/databind/node/POJONodeTest.java +++ b/src/test/java/tools/jackson/databind/node/POJONodeTest.java @@ -74,4 +74,5 @@ public void testAddJava8DateAsPojo() throws Exception String msg = result.path("test").asString(); assertEquals(dt, LocalDateTime.parse(msg)); } + }