From 4b0d5bc87d8c7b457a4d5d85c504dd80c8f575da Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Fri, 12 Jun 2020 19:08:25 -0700 Subject: [PATCH] Yet more refactoring for coercion config --- .../databind/DeserializationContext.java | 3 +- .../deser/std/NumberDeserializers.java | 57 ++------ .../std/PrimitiveArrayDeserializers.java | 6 +- .../databind/deser/std/StdDeserializer.java | 134 +++++++++++++++--- .../convert/CoerceFloatToIntTest.java | 19 ++- .../databind/deser/jdk/JDKScalarsTest.java | 2 +- .../jackson/databind/node/ArrayNodeTest.java | 2 +- 7 files changed, 150 insertions(+), 73 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java b/src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java index c65c6b12ac..83b12e1a29 100644 --- a/src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java +++ b/src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java @@ -2048,8 +2048,9 @@ protected String _shapeForToken(JsonToken t) { return "Embedded Object"; case VALUE_NUMBER_FLOAT: + return "Floating-point value"; case VALUE_NUMBER_INT: - return "Number value"; + return "Integer value"; case VALUE_STRING: return "String value"; diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/NumberDeserializers.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/NumberDeserializers.java index efe0153685..739d40c0e2 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/NumberDeserializers.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/NumberDeserializers.java @@ -266,7 +266,10 @@ public Byte deserialize(JsonParser p, DeserializationContext ctxt) throws IOExce if (p.hasToken(JsonToken.VALUE_NUMBER_INT)) { return p.getByteValue(); } - return _parseByte(p, ctxt, _valueClass); +// if (_primitive) { +// return _parseBytePrimitive(ctxt, p, _valueClass); +// } + return _parseByte(ctxt, p, _valueClass); } } @@ -288,55 +291,13 @@ public ShortDeserializer(Class cls, Short nvl) public Short deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { - return _parseShort(p, ctxt); - } - - protected Short _parseShort(JsonParser p, DeserializationContext ctxt) throws IOException - { - JsonToken t = p.currentToken(); - if (t == JsonToken.VALUE_NUMBER_INT) { - return p.getShortValue(); - } - if (t == JsonToken.VALUE_STRING) { // let's do implicit re-parse - String text = p.getText(); - CoercionAction act = _checkFromStringCoercion(ctxt, text); - if (act == CoercionAction.AsNull) { - return (Short) getNullValue(ctxt); - } - if (act == CoercionAction.AsEmpty) { - return (Short) getEmptyValue(ctxt); - } - text = text.trim(); - if (_hasTextualNull(text)) { - return (Short) _coerceTextualNull(ctxt, _primitive); - } - int value; - try { - value = NumberInput.parseInt(text); - } catch (IllegalArgumentException iae) { - return (Short) ctxt.handleWeirdStringValue(_valueClass, text, - "not a valid Short value"); - } - // So far so good: but does it fit? - if (_shortOverflow(value)) { - return (Short) ctxt.handleWeirdStringValue(_valueClass, text, - "overflow, value cannot be represented as 16-bit value"); - } - return Short.valueOf((short) value); - } - if (t == JsonToken.VALUE_NUMBER_FLOAT) { - if (!ctxt.isEnabled(DeserializationFeature.ACCEPT_FLOAT_AS_INT)) { - _failDoubleToIntCoercion(p, ctxt, "Short"); - } + if (p.hasToken(JsonToken.VALUE_NUMBER_INT)) { return p.getShortValue(); } - if (t == JsonToken.VALUE_NULL) { - return (Short) _coerceNullToken(ctxt, _primitive); - } - if (t == JsonToken.START_ARRAY) { - return _deserializeFromArray(p, ctxt); - } - return (Short) ctxt.handleUnexpectedToken(_valueClass, p); +// if (_primitive) { +// return _parseShortPrimitive(ctxt, p, _valueClass); +// } + return _parseShort(ctxt, p, _valueClass); } } diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/PrimitiveArrayDeserializers.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/PrimitiveArrayDeserializers.java index 20c87d35b3..85a7cfcca7 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/PrimitiveArrayDeserializers.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/PrimitiveArrayDeserializers.java @@ -508,7 +508,7 @@ public byte[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOEx _verifyNullForPrimitive(ctxt); value = (byte) 0; } else { - value = _parseBytePrimitive(ctxt, p, handledType()); + value = _parseBytePrimitive(ctxt, p, Byte.TYPE); } } if (ix >= chunk.length) { @@ -602,7 +602,7 @@ public short[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOE _verifyNullForPrimitive(ctxt); value = (short) 0; } else { - value = _parseShortPrimitive(p, ctxt); + value = _parseShortPrimitive(ctxt, p, Short.TYPE); } if (ix >= chunk.length) { chunk = builder.appendCompletedChunk(chunk, ix); @@ -619,7 +619,7 @@ public short[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOE @Override protected short[] handleSingleElementUnwrapped(JsonParser p, DeserializationContext ctxt) throws IOException { - return new short[] { _parseShortPrimitive(p, ctxt) }; + return new short[] { _parseShortPrimitive(ctxt, p, Short.TYPE) }; } @Override diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/StdDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/StdDeserializer.java index 7f15e81a54..9f7dc776e8 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/StdDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/StdDeserializer.java @@ -365,6 +365,8 @@ protected final boolean _parseBooleanPrimitive(JsonParser p, DeserializationCont * same as {@link #handledType}, and not necessarily {@code boolean} * (may be {@code boolean[]} or {@code AtomicBoolean} for example); * used for coercion config access + * + * @since 2.12 */ protected final boolean _parseBooleanPrimitive(DeserializationContext ctxt, JsonParser p, Class targetType) @@ -484,6 +486,14 @@ protected boolean _parseBooleanFromInt(JsonParser p, DeserializationContext ctxt return !"0".equals(p.getText()); } + @Deprecated // since 2.12, use overloaded variant + protected final byte _parseBytePrimitive(JsonParser p, DeserializationContext ctxt) throws IOException { + return _parseBytePrimitive(ctxt, p, Byte.TYPE); + } + + /** + * @since 2.12 + */ protected final byte _parseBytePrimitive(DeserializationContext ctxt, JsonParser p, Class targetType) throws IOException @@ -491,14 +501,14 @@ protected final byte _parseBytePrimitive(DeserializationContext ctxt, JsonParser final JsonToken t = p.currentToken(); if (t == JsonToken.VALUE_NUMBER_INT) return p.getByteValue(); if (t == JsonToken.VALUE_NULL) { - return (Byte) _coerceNullToken(ctxt, true); + _verifyNullForPrimitive(ctxt); + return (byte) 0; } if (t == JsonToken.VALUE_STRING) { // let's do implicit re-parse String text = p.getText(); CoercionAction act = _checkFromStringCoercion(ctxt, text, LogicalType.Integer, targetType); if (act == CoercionAction.AsNull) { -// _verifyNullForPrimitiveCoercion(ctxt, text); return (byte) 0; // no need to check as does not come from `null`, explicit coercion } if (act == CoercionAction.AsEmpty) { @@ -542,10 +552,13 @@ protected final byte _parseBytePrimitive(DeserializationContext ctxt, JsonParser _verifyEndArrayForSingle(p, ctxt); return parsed; } - return ((Byte) ctxt.handleUnexpectedToken(targetType, p)).byteValue(); + return ((Byte) ctxt.handleUnexpectedToken(ctxt.constructType(targetType), p)).byteValue(); } - protected Byte _parseByte(JsonParser p, DeserializationContext ctxt, + /** + * @since 2.12 + */ + protected Byte _parseByte(DeserializationContext ctxt, JsonParser p, Class targetType) throws IOException { final JsonToken t = p.currentToken(); @@ -571,13 +584,13 @@ protected Byte _parseByte(JsonParser p, DeserializationContext ctxt, try { value = NumberInput.parseInt(text); } catch (IllegalArgumentException iae) { - return (Byte) ctxt.handleWeirdStringValue(_valueClass, text, + return (Byte) ctxt.handleWeirdStringValue(targetType, text, "not a valid Byte value"); } // So far so good: but does it fit? // as per [JACKSON-804], allow range up to 255, inclusive if (_byteOverflow(value)) { - return (Byte) ctxt.handleWeirdStringValue(_valueClass, text, + return (Byte) ctxt.handleWeirdStringValue(targetType, text, "overflow, value cannot be represented as 8-bit value"); // fall-through for deferred fails } @@ -596,25 +609,114 @@ protected Byte _parseByte(JsonParser p, DeserializationContext ctxt, if (t == JsonToken.START_ARRAY) { // [databind#381] return (Byte) _deserializeFromArray(p, ctxt); } - return (Byte) ctxt.handleUnexpectedToken(_valueClass, p); + return (Byte) ctxt.handleUnexpectedToken(ctxt.constructType(targetType), p); + } + + @Deprecated // since 2.12, use overloaded variant + protected final short _parseShortPrimitive(JsonParser p, DeserializationContext ctxt) throws IOException { + return _parseShortPrimitive(ctxt, p, Short.TYPE); } - protected final short _parseShortPrimitive(JsonParser p, DeserializationContext ctxt) + /** + * @since 2.12 + */ + protected final short _parseShortPrimitive(DeserializationContext ctxt, JsonParser p, + Class targetType) throws IOException { final JsonToken t = p.currentToken(); if (t == JsonToken.VALUE_NUMBER_INT) return p.getShortValue(); if (t == JsonToken.VALUE_NULL) { - return (Byte) _coerceNullToken(ctxt, true); + _verifyNullForPrimitive(ctxt); + return (short) 0; + } + if (t == JsonToken.VALUE_STRING) { // let's do implicit re-parse + String text = p.getText(); + CoercionAction act = _checkFromStringCoercion(ctxt, text, + LogicalType.Integer, targetType); + if (act == CoercionAction.AsNull) { + return (short) 0; // no need to check as does not come from `null`, explicit coercion + } + if (act == CoercionAction.AsEmpty) { + return (short) 0; + } + text = text.trim(); + if (_hasTextualNull(text)) { + _verifyNullForPrimitiveCoercion(ctxt, text); + return (short) 0; + } + int value; + try { + value = NumberInput.parseInt(text); + } catch (IllegalArgumentException iae) { + return (Short) ctxt.handleWeirdStringValue(targetType, text, + "not a valid Short value"); + } + if (_shortOverflow(value)) { + return (Short) ctxt.handleWeirdStringValue(targetType, text, + "overflow, value cannot be represented as 16-bit value"); + } + return (short) value; + } + // 12-Jun-2020, tatu: For some reason calling `_deserializeFromArray()` won't work so: + if (t == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { + p.nextToken(); + final short parsed = _parseShortPrimitive(ctxt, p, targetType); + _verifyEndArrayForSingle(p, ctxt); + return parsed; + } + return ((Short) ctxt.handleUnexpectedToken(ctxt.constructType(targetType), p)).shortValue(); + } + + protected Short _parseShort(DeserializationContext ctxt, JsonParser p, + Class targetType) throws IOException + { + final JsonToken t = p.currentToken(); + if (t == JsonToken.VALUE_NUMBER_INT) return p.getShortValue(); + if (t == JsonToken.VALUE_NULL) { + return (Short) _coerceNullToken(ctxt, false); + } + if (t == JsonToken.VALUE_STRING) { // let's do implicit re-parse + String text = p.getText(); + CoercionAction act = _checkFromStringCoercion(ctxt, text); + if (act == CoercionAction.AsNull) { + return (Short) getNullValue(ctxt); + } + if (act == CoercionAction.AsEmpty) { + return (Short) getEmptyValue(ctxt); + } + text = text.trim(); + if (_hasTextualNull(text)) { + return (Short) _coerceTextualNull(ctxt, false); + } + int value; + try { + value = NumberInput.parseInt(text); + } catch (IllegalArgumentException iae) { + return (Short) ctxt.handleWeirdStringValue(_valueClass, text, + "not a valid Short value"); + } + // So far so good: but does it fit? + if (_shortOverflow(value)) { + return (Short) ctxt.handleWeirdStringValue(_valueClass, text, + "overflow, value cannot be represented as 16-bit value"); + } + return Short.valueOf((short) value); + } + if (t == JsonToken.VALUE_NUMBER_FLOAT) { + CoercionAction act = _checkFloatToIntCoercion(ctxt, p, targetType); + if (act == CoercionAction.AsNull) { + return (Short) getNullValue(ctxt); + } + if (act == CoercionAction.AsEmpty) { + return (Short) getEmptyValue(ctxt); + } + return p.getShortValue(); } - int value = _parseIntPrimitive(p, ctxt); - // So far so good: but does it fit? - if (_shortOverflow(value)) { - Number v = (Number) ctxt.handleWeirdStringValue(_valueClass, String.valueOf(value), - "overflow, value cannot be represented as 16-bit value"); - return _nonNullNumber(v).shortValue(); + if (t == JsonToken.START_ARRAY) { + return (Short)_deserializeFromArray(p, ctxt); } - return (short) value; + return (Short) ctxt.handleUnexpectedToken(ctxt.constructType(targetType), p); } protected final int _parseIntPrimitive(JsonParser p, DeserializationContext ctxt) diff --git a/src/test/java/com/fasterxml/jackson/databind/convert/CoerceFloatToIntTest.java b/src/test/java/com/fasterxml/jackson/databind/convert/CoerceFloatToIntTest.java index 4bd362a663..ea63ba71ca 100644 --- a/src/test/java/com/fasterxml/jackson/databind/convert/CoerceFloatToIntTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/convert/CoerceFloatToIntTest.java @@ -57,9 +57,9 @@ public void testLegacyFailDoubleToInt() throws Exception _verifyCoerceFail(READER_LEGACY_FAIL, LongWrapper.class, "{\"l\": 7.7 }"); _verifyCoerceFail(READER_LEGACY_FAIL, long[].class, "[ -1.35 ]"); - _verifyCoerceFail(READER_LEGACY_FAIL, Short.class, "0.5"); - _verifyCoerceFail(READER_LEGACY_FAIL, Short.TYPE, "-2.5"); - _verifyCoerceFail(READER_LEGACY_FAIL, short[].class, "[ -1.35 ]"); + _verifyCoerceFailShort(READER_LEGACY_FAIL, Short.class, "0.5"); + _verifyCoerceFailShort(READER_LEGACY_FAIL, Short.TYPE, "-2.5"); + _verifyCoerceFailShort(READER_LEGACY_FAIL, short[].class, "[ -1.35 ]"); _verifyCoerceFail(READER_LEGACY_FAIL, BigInteger.class, "25236.256"); } @@ -74,6 +74,19 @@ private void _verifyCoerceFail(ObjectReader r, Class targetType, verifyException(e, "Cannot coerce a floating-point"); } } + + private void _verifyCoerceFailShort(ObjectReader r, Class targetType, + String doc) throws Exception + { + try { + r.forType(targetType).readValue(doc); + fail("Should not pass"); + } catch (MismatchedInputException e) { + verifyException(e, + "Cannot deserialize value of type `short` from Floating-point value", + "Cannot coerce Floating-point"); + } + } public void testDoubleToLong() throws Exception { diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKScalarsTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKScalarsTest.java index 0d334f2e16..57cfea2cb3 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKScalarsTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKScalarsTest.java @@ -920,7 +920,7 @@ public void testInvalidStringCoercionFail() throws IOException // char[] is special, cannot use generalized test here // _testInvalidStringCoercionFail(char[].class); - _testInvalidStringCoercionFail(short[].class); + _testInvalidStringCoercionFail(short[].class, "short"); _testInvalidStringCoercionFail(int[].class); _testInvalidStringCoercionFail(long[].class); _testInvalidStringCoercionFail(float[].class); diff --git a/src/test/java/com/fasterxml/jackson/databind/node/ArrayNodeTest.java b/src/test/java/com/fasterxml/jackson/databind/node/ArrayNodeTest.java index 3066bf852b..08bd47d98c 100644 --- a/src/test/java/com/fasterxml/jackson/databind/node/ArrayNodeTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/node/ArrayNodeTest.java @@ -359,7 +359,7 @@ public void testSimpleMismatch() throws Exception mapper.readValue(" 123 ", ArrayNode.class); fail("Should not pass"); } catch (MismatchedInputException e) { - verifyException(e, "from Number value (token `JsonToken.VALUE_NUMBER_INT`)"); + verifyException(e, "from Integer value (token `JsonToken.VALUE_NUMBER_INT`)"); } } }