From 1fdb43182c11e74c8373591f0736ac8676ad926d Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Wed, 16 Dec 2020 20:34:55 -0800 Subject: [PATCH] Start work on fixing #120, first for int/long, afterburner, 2.12 --- .../deser/OptimizedSettableBeanProperty.java | 336 +++++++++++---- .../afterburner/AfterburnerTestBase.java | 44 ++ .../deser/convert/CoerceFloatToIntTest.java | 342 +++++++++++++++ .../deser/convert/CoerceJDKScalarsTest.java | 245 +++++++++++ .../deser/convert/CoerceStringToIntsTest.java | 315 ++++++++++++++ ...ailOnPrimitiveFromNullDeserialization.java | 6 +- .../filter/IgnoreCreatorProp1317Test.java | 5 +- .../deser/jdk/JDKScalarsDeserTest.java | 394 ++++++++---------- .../ser/OptimizedBeanPropertyWriter.java | 1 - ...ailOnPrimitiveFromNullDeserialization.java | 2 +- 10 files changed, 1369 insertions(+), 321 deletions(-) create mode 100644 afterburner/src/test/java/com/fasterxml/jackson/module/afterburner/deser/convert/CoerceFloatToIntTest.java create mode 100644 afterburner/src/test/java/com/fasterxml/jackson/module/afterburner/deser/convert/CoerceJDKScalarsTest.java create mode 100644 afterburner/src/test/java/com/fasterxml/jackson/module/afterburner/deser/convert/CoerceStringToIntsTest.java rename afterburner/src/test/java/com/fasterxml/jackson/module/afterburner/deser/{ => convert}/TestFailOnPrimitiveFromNullDeserialization.java (92%) rename blackbird/src/test/java/com/fasterxml/jackson/module/blackbird/deser/{ => convert}/TestFailOnPrimitiveFromNullDeserialization.java (97%) diff --git a/afterburner/src/main/java/com/fasterxml/jackson/module/afterburner/deser/OptimizedSettableBeanProperty.java b/afterburner/src/main/java/com/fasterxml/jackson/module/afterburner/deser/OptimizedSettableBeanProperty.java index 1f6398f4..531e84dc 100644 --- a/afterburner/src/main/java/com/fasterxml/jackson/module/afterburner/deser/OptimizedSettableBeanProperty.java +++ b/afterburner/src/main/java/com/fasterxml/jackson/module/afterburner/deser/OptimizedSettableBeanProperty.java @@ -8,8 +8,11 @@ import com.fasterxml.jackson.core.JsonParser.NumberType; import com.fasterxml.jackson.core.io.NumberInput; import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.cfg.CoercionAction; +import com.fasterxml.jackson.databind.cfg.CoercionInputShape; import com.fasterxml.jackson.databind.deser.*; import com.fasterxml.jackson.databind.deser.impl.NullsConstantProvider; +import com.fasterxml.jackson.databind.type.LogicalType; import com.fasterxml.jackson.databind.util.ClassUtil; /** @@ -79,7 +82,7 @@ public final SettableBeanProperty withValueDeserializer(JsonDeserializer dese */ @Override - public abstract void deserializeAndSet(JsonParser jp, DeserializationContext ctxt, + public abstract void deserializeAndSet(JsonParser p, DeserializationContext ctxt, Object arg2) throws IOException; @Override @@ -102,7 +105,7 @@ public abstract void deserializeAndSet(JsonParser jp, DeserializationContext ctx */ @Override - public abstract Object deserializeSetAndReturn(JsonParser jp, + public abstract Object deserializeSetAndReturn(JsonParser p, DeserializationContext ctxt, Object instance) throws IOException; @@ -222,113 +225,153 @@ protected final boolean _deserializeBoolean(JsonParser p, DeserializationContext return (Boolean) ctxt.handleUnexpectedToken(Boolean.TYPE, p); } - protected final short _deserializeShort(JsonParser jp, DeserializationContext ctxt) - throws IOException - { - int value = _deserializeInt(jp, ctxt); - // So far so good: but does it fit? - if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) { - throw ctxt.weirdStringException(String.valueOf(value), - Short.TYPE, "overflow, value can not be represented as 16-bit value"); - } - return (short) value; - } - + // 16-Dec-2020, tatu: Copied from "StdDeserializer._parseIntPrimitive()" verbatim: + // + // @since 2.12.1 protected final int _deserializeInt(JsonParser p, DeserializationContext ctxt) throws IOException { - if (p.hasToken(JsonToken.VALUE_NUMBER_INT)) { - return p.getIntValue(); - } - JsonToken t = p.getCurrentToken(); - if (t == JsonToken.VALUE_STRING) { // let's do implicit re-parse - _verifyScalarCoercion(ctxt, p, "int"); - String text = p.getText().trim(); - if (_hasTextualNull(text)) { + String text; + switch (p.currentTokenId()) { + case JsonTokenId.ID_STRING: + text = p.getText(); + break; + case JsonTokenId.ID_NUMBER_FLOAT: + final CoercionAction act = _checkFloatToIntCoercion(p, ctxt, Integer.TYPE); + if (act == CoercionAction.AsNull) { return 0; } - try { - int len = text.length(); - if (len > 9) { - long l = Long.parseLong(text); - if (l < Integer.MIN_VALUE || l > Integer.MAX_VALUE) { - throw ctxt.weirdStringException(text, Integer.TYPE, - "Overflow: numeric value ("+text+") out of range of int ("+Integer.MIN_VALUE+" - "+Integer.MAX_VALUE+")"); - } - return (int) l; - } - if (len == 0) { - return 0; - } - return NumberInput.parseInt(text); - } catch (IllegalArgumentException iae) { - throw ctxt.weirdStringException(text, Integer.TYPE, "not a valid int value"); - } - } - if (t == JsonToken.VALUE_NUMBER_FLOAT) { - if (!ctxt.isEnabled(DeserializationFeature.ACCEPT_FLOAT_AS_INT)) { - _failDoubleToIntCoercion(p, ctxt, "int"); + if (act == CoercionAction.AsEmpty) { + return 0; } return p.getValueAsInt(); - } - if (t == JsonToken.VALUE_NULL) { - if (ctxt.isEnabled(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)) { - _failNullToPrimitiveCoercion(ctxt, "int"); + case JsonTokenId.ID_NUMBER_INT: + return p.getIntValue(); + case JsonTokenId.ID_NULL: + _verifyNullForPrimitive(ctxt, Integer.TYPE); + return 0; + // 16-Dec-2020, tatu: not sure if this will work (no deserializer to pass), + // but we'll do our best + case JsonTokenId.ID_START_OBJECT: + text = ctxt.extractScalarFromObject(p, /* deserializer */ null, Integer.TYPE); + break; + case JsonTokenId.ID_START_ARRAY: + if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { + p.nextToken(); + final int parsed = _deserializeInt(p, ctxt); + _verifyEndArrayForSingle(p, ctxt); + return parsed; } + // fall through to fail + default: + return ((Number) ctxt.handleUnexpectedToken(Integer.TYPE, p)).intValue(); + } + + final CoercionAction act = _checkFromStringCoercion(ctxt, text, + LogicalType.Integer, Integer.TYPE); + if (act == CoercionAction.AsNull) { + return 0; // no need to check as does not come from `null`, explicit coercion + } + if (act == CoercionAction.AsEmpty) { return 0; } - if (t == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { - p.nextToken(); - final int parsed = _deserializeInt(p, ctxt); - t = p.nextToken(); - if (t != JsonToken.END_ARRAY) { - _handleMissingEndArrayForSingle(p, ctxt); - } - return parsed; + text = text.trim(); + if (_hasTextualNull(text)) { + _verifyNullForPrimitiveCoercion(ctxt, Integer.TYPE, text); + return 0; } - // Otherwise, no can do: - return (Integer) ctxt.handleUnexpectedToken(Integer.TYPE, p); + return _parseIntPrimitive(ctxt, text); } + private final int _parseIntPrimitive(DeserializationContext ctxt, String text) throws IOException + { + try { + if (text.length() > 9) { + long l = Long.parseLong(text); + if (_intOverflow(l)) { + Number v = (Number) ctxt.handleWeirdStringValue(Integer.TYPE, text, + "Overflow: numeric value (%s) out of range of int (%d -%d)", + text, Integer.MIN_VALUE, Integer.MAX_VALUE); + return _nonNullNumber(v).intValue(); + } + return (int) l; + } + return NumberInput.parseInt(text); + } catch (IllegalArgumentException iae) { + Number v = (Number) ctxt.handleWeirdStringValue(Integer.TYPE, text, + "not a valid `int` value"); + return _nonNullNumber(v).intValue(); + } + } + + // 16-Dec-2020, tatu: Copied from "StdDeserializer._parseLongPrimitive()" verbatim: + // + // @since 2.12.1 protected final long _deserializeLong(JsonParser p, DeserializationContext ctxt) throws IOException { + String text; switch (p.currentTokenId()) { - case JsonTokenId.ID_NUMBER_INT: - return p.getLongValue(); + case JsonTokenId.ID_STRING: + text = p.getText(); + break; case JsonTokenId.ID_NUMBER_FLOAT: - if (!ctxt.isEnabled(DeserializationFeature.ACCEPT_FLOAT_AS_INT)) { - _failDoubleToIntCoercion(p, ctxt, "long"); + final CoercionAction act = _checkFloatToIntCoercion(p, ctxt, Long.TYPE); + if (act == CoercionAction.AsNull) { + return 0; } - return p.getValueAsLong(); - case JsonTokenId.ID_STRING: - _verifyScalarCoercion(ctxt, p, "long"); - String text = p.getText().trim(); - if (text.length() == 0 || _hasTextualNull(text)) { - return 0L; + if (act == CoercionAction.AsEmpty) { + return 0; } - try { - return NumberInput.parseLong(text); - } catch (IllegalArgumentException iae) { } - throw ctxt.weirdStringException(text, Long.TYPE, "not a valid long value"); + return p.getValueAsInt(); + case JsonTokenId.ID_NUMBER_INT: + return p.getIntValue(); case JsonTokenId.ID_NULL: - if (ctxt.isEnabled(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)) { - _failNullToPrimitiveCoercion(ctxt, "long"); - } - return 0L; + _verifyNullForPrimitive(ctxt, Long.TYPE); + return 0; + // 16-Dec-2020, tatu: not sure if this will work (no deserializer to pass), + // but we'll do our best + case JsonTokenId.ID_START_OBJECT: + text = ctxt.extractScalarFromObject(p, /* deserializer */ null, Long.TYPE); + break; case JsonTokenId.ID_START_ARRAY: if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { p.nextToken(); final long parsed = _deserializeLong(p, ctxt); - JsonToken t = p.nextToken(); - if (t != JsonToken.END_ARRAY) { - _handleMissingEndArrayForSingle(p, ctxt); - } + _verifyEndArrayForSingle(p, ctxt); return parsed; } - break; + // fall through to fail + default: + return ((Number) ctxt.handleUnexpectedToken(Long.TYPE, p)).intValue(); + } + + final CoercionAction act = _checkFromStringCoercion(ctxt, text, + LogicalType.Integer, Long.TYPE); + if (act == CoercionAction.AsNull) { + return 0; // no need to check as does not come from `null`, explicit coercion + } + if (act == CoercionAction.AsEmpty) { + return 0; + } + text = text.trim(); + if (_hasTextualNull(text)) { + _verifyNullForPrimitiveCoercion(ctxt, Long.TYPE, text); + return 0; + } + return _parseLongPrimitive(ctxt, text); + } + + private final long _parseLongPrimitive(DeserializationContext ctxt, String text) throws IOException + { + try { + return NumberInput.parseLong(text); + } catch (IllegalArgumentException iae) { } + { + Number v = (Number) ctxt.handleWeirdStringValue(Long.TYPE, text, + "not a valid `long` value"); + return _nonNullNumber(v).longValue(); } - return (Long) ctxt.handleUnexpectedToken(Long.TYPE, p); } protected final String _deserializeString(JsonParser p, DeserializationContext ctxt) throws IOException @@ -384,21 +427,13 @@ protected final boolean _deserializeBooleanFromOther(JsonParser p, Deserializati // // More helper methods from StdDeserializer - protected void _failNullToPrimitiveCoercion(DeserializationContext ctxt, String type) throws JsonMappingException + private void _failNullToPrimitiveCoercion(DeserializationContext ctxt, String type) throws JsonMappingException { ctxt.reportInputMismatch(getType(), "Cannot map `null` into type %s (set DeserializationConfig.DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES to 'false' to allow)", type); } - protected void _failDoubleToIntCoercion(JsonParser p, DeserializationContext ctxt, - String type) throws IOException - { - ctxt.reportInputMismatch(getType(), - "Can not coerce a floating-point value (%s) into %s; enable `DeserializationFeature.ACCEPT_FLOAT_AS_INT` to allow", - p.getValueAsString(), type); - } - private void _verifyScalarCoercion(DeserializationContext ctxt, JsonParser parser, String type) throws IOException { MapperFeature feat = MapperFeature.ALLOW_COERCION_OF_SCALARS; if (!ctxt.isEnabled(feat)) { @@ -412,10 +447,32 @@ private void _verifyScalarCoercion(DeserializationContext ctxt, JsonParser parse } } - protected boolean _hasTextualNull(String value) { + private boolean _hasTextualNull(String value) { return "null".equals(value); } + private final boolean _intOverflow(long value) { + return (value < Integer.MIN_VALUE || value > Integer.MAX_VALUE); + } + + private Number _nonNullNumber(Number n) { + if (n == null) { + n = Integer.valueOf(0); + } + return n; + } + + protected final static boolean _isBlank(String text) + { + final int len = text.length(); + for (int i = 0; i < len; ++i) { + if (text.charAt(i) > 0x0020) { + return false; + } + } + return true; + } + /** * Helper method used to check whether given deserializer is the default * deserializer implementation: this is necessary to avoid overriding custom @@ -427,7 +484,15 @@ protected boolean _isDefaultDeserializer(JsonDeserializer deser) { || ClassUtil.isJacksonStdImpl(deser); } - protected void _handleMissingEndArrayForSingle(JsonParser p, DeserializationContext ctxt) + private void _verifyEndArrayForSingle(JsonParser p, DeserializationContext ctxt) throws IOException + { + JsonToken t = p.nextToken(); + if (t != JsonToken.END_ARRAY) { + _handleMissingEndArrayForSingle(p, ctxt); + } + } + + private void _handleMissingEndArrayForSingle(JsonParser p, DeserializationContext ctxt) throws IOException { JavaType type = getType(); @@ -437,4 +502,91 @@ protected void _handleMissingEndArrayForSingle(JsonParser p, DeserializationCont // 05-May-2016, tatu: Should recover somehow (maybe skip until END_ARRAY); // but for now just fall through } + + /* + /********************************************************************** + /* New methods in 2.12.1 to align CoercionConfig handling with databind + /* (copied from "StdDeserializer") + /********************************************************************** + */ + + private CoercionAction _checkFloatToIntCoercion(JsonParser p, DeserializationContext ctxt, + Class rawTargetType) + throws IOException + { + final CoercionAction act = ctxt.findCoercionAction(LogicalType.Integer, + rawTargetType, CoercionInputShape.Float); + if (act == CoercionAction.Fail) { + return _checkCoercionFail(ctxt, act, rawTargetType, p.getNumberValue(), + "Floating-point value ("+p.getText()+")"); + } + return act; + } + + private CoercionAction _checkFromStringCoercion(DeserializationContext ctxt, String value, + LogicalType logicalType, Class rawTargetType) + throws IOException + { + final CoercionAction act; + + if (value.isEmpty()) { + act = ctxt.findCoercionAction(logicalType, rawTargetType, + CoercionInputShape.EmptyString); + return _checkCoercionFail(ctxt, act, rawTargetType, value, + "empty String (\"\")"); + } else if (_isBlank(value)) { + act = ctxt.findCoercionFromBlankString(logicalType, rawTargetType, CoercionAction.Fail); + return _checkCoercionFail(ctxt, act, rawTargetType, value, + "blank String (all whitespace)"); + } else { + act = ctxt.findCoercionAction(logicalType, rawTargetType, CoercionInputShape.String); + if (act == CoercionAction.Fail) { + // since it MIGHT (but might not), create desc here, do not use helper + ctxt.reportInputMismatch(this, +"Cannot coerce String value (\"%s\") to %s (but might if coercion using `CoercionConfig` was enabled)", +value, _coercedTypeDesc(rawTargetType)); + } + } + return act; + } + + private CoercionAction _checkCoercionFail(DeserializationContext ctxt, + CoercionAction act, Class targetType, Object inputValue, + String inputDesc) + throws IOException + { + if (act == CoercionAction.Fail) { + // 16-Dec-2020, tatu: Let's hope `null` for deserializer is ok... + ctxt.reportBadCoercion(null, targetType, inputValue, +"Cannot coerce %s to %s (but could if coercion was enabled using `CoercionConfig`)", +inputDesc, _coercedTypeDesc(targetType)); + } + return act; + } + + private void _verifyNullForPrimitive(DeserializationContext ctxt, + Class targetType) throws JsonMappingException + { + if (ctxt.isEnabled(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)) { + ctxt.reportInputMismatch(this, +"Cannot coerce `null` to %s (disable `DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES` to allow)", + _coercedTypeDesc(targetType)); + } + } + + private final void _verifyNullForPrimitiveCoercion(DeserializationContext ctxt, + Class targetType, String str) throws JsonMappingException + { + if (ctxt.isEnabled(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)) { + String strDesc = str.isEmpty() ? "empty String (\"\")" : String.format("String \"%s\"", str); + ctxt.reportInputMismatch(this, +"Cannot coerce %s to %s (disable `DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES` to allow)", + strDesc, _coercedTypeDesc(targetType)); + } + } + + // Simplified as we only ever get simple scalar types + private String _coercedTypeDesc(Class targetType) { + return ClassUtil.getClassDescription(targetType) +" value"; + } } diff --git a/afterburner/src/test/java/com/fasterxml/jackson/module/afterburner/AfterburnerTestBase.java b/afterburner/src/test/java/com/fasterxml/jackson/module/afterburner/AfterburnerTestBase.java index a6796390..da716764 100644 --- a/afterburner/src/test/java/com/fasterxml/jackson/module/afterburner/AfterburnerTestBase.java +++ b/afterburner/src/test/java/com/fasterxml/jackson/module/afterburner/AfterburnerTestBase.java @@ -16,6 +16,34 @@ public abstract class AfterburnerTestBase extends junit.framework.TestCase public enum ABC { A, B, C; } + protected static class BooleanWrapper { + public Boolean b; + + public BooleanWrapper() { } + public BooleanWrapper(Boolean value) { b = value; } + } + + protected static class IntWrapper { + public int i; + + public IntWrapper() { } + public IntWrapper(int value) { i = value; } + } + + protected static class LongWrapper { + public long l; + + public LongWrapper() { } + public LongWrapper(long value) { l = value; } + } + + protected static class DoubleWrapper { + public double d; + + public DoubleWrapper() { } + public DoubleWrapper(double value) { d = value; } + } + // since 2.8 public static class Point { public int x, y; @@ -257,10 +285,26 @@ protected void verifyException(Throwable e, String... matches) */ public String quote(String str) { + return q(str); + } + + public String q(String str) { return '"'+str+'"'; } protected static String aposToQuotes(String json) { + return a2q(json); + } + + protected static String a2q(String json) { return json.replace("'", "\""); } + + protected byte[] utf8Bytes(String str) { + try { + return str.getBytes("UTF-8"); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } } diff --git a/afterburner/src/test/java/com/fasterxml/jackson/module/afterburner/deser/convert/CoerceFloatToIntTest.java b/afterburner/src/test/java/com/fasterxml/jackson/module/afterburner/deser/convert/CoerceFloatToIntTest.java new file mode 100644 index 00000000..ff694081 --- /dev/null +++ b/afterburner/src/test/java/com/fasterxml/jackson/module/afterburner/deser/convert/CoerceFloatToIntTest.java @@ -0,0 +1,342 @@ +package com.fasterxml.jackson.module.afterburner.deser.convert; + +import java.math.BigInteger; +import java.util.*; +import java.util.concurrent.atomic.AtomicLong; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.cfg.CoercionAction; +import com.fasterxml.jackson.databind.cfg.CoercionInputShape; +import com.fasterxml.jackson.databind.exc.InvalidFormatException; +import com.fasterxml.jackson.databind.exc.MismatchedInputException; +import com.fasterxml.jackson.databind.type.LogicalType; +import com.fasterxml.jackson.module.afterburner.AfterburnerTestBase; + +//Copied from "jackson-databind" as of 2.12.1 +public class CoerceFloatToIntTest extends AfterburnerTestBase +{ + private final ObjectMapper DEFAULT_MAPPER = newAfterburnerMapper(); + private final ObjectReader READER_LEGACY_FAIL = DEFAULT_MAPPER.reader() + .without(DeserializationFeature.ACCEPT_FLOAT_AS_INT); + + private final ObjectMapper MAPPER_TO_EMPTY; { + MAPPER_TO_EMPTY = newAfterburnerMapper(); + MAPPER_TO_EMPTY.coercionConfigFor(LogicalType.Integer) + .setCoercion(CoercionInputShape.Float, CoercionAction.AsEmpty); + } + + private final ObjectMapper MAPPER_TRY_CONVERT; { + MAPPER_TRY_CONVERT = newAfterburnerMapper(); + MAPPER_TRY_CONVERT.coercionConfigFor(LogicalType.Integer) + .setCoercion(CoercionInputShape.Float, CoercionAction.TryConvert); + } + + private final ObjectMapper MAPPER_TO_NULL; { + MAPPER_TO_NULL = newAfterburnerMapper(); + MAPPER_TO_NULL.coercionConfigFor(LogicalType.Integer) + .setCoercion(CoercionInputShape.Float, CoercionAction.AsNull); + } + + private final ObjectMapper MAPPER_TO_FAIL; { + MAPPER_TO_FAIL = newAfterburnerMapper(); + MAPPER_TO_FAIL.coercionConfigFor(LogicalType.Integer) + .setCoercion(CoercionInputShape.Float, CoercionAction.Fail); + } + + /* + /******************************************************** + /* Test methods, defaults (legacy) + /******************************************************** + */ + + public void testLegacyDoubleToIntCoercion() throws Exception + { + // by default, should be ok + Integer I = DEFAULT_MAPPER.readValue(" 1.25 ", Integer.class); + assertEquals(1, I.intValue()); + { + IntWrapper w = DEFAULT_MAPPER.readValue("{\"i\":-2.25 }", IntWrapper.class); + assertEquals(-2, w.i); + int[] arr = DEFAULT_MAPPER.readValue("[ 1.25 ]", int[].class); + assertEquals(1, arr[0]); + } + + Long L = DEFAULT_MAPPER.readValue(" 3.33 ", Long.class); + assertEquals(3L, L.longValue()); + { + LongWrapper w = DEFAULT_MAPPER.readValue("{\"l\":-2.25 }", LongWrapper.class); + assertEquals(-2L, w.l); + long[] arr = DEFAULT_MAPPER.readValue("[ 1.25 ]", long[].class); + assertEquals(1, arr[0]); + } + + Short S = DEFAULT_MAPPER.readValue("42.33", Short.class); + assertEquals(42, S.intValue()); + + BigInteger biggie = DEFAULT_MAPPER.readValue("95.3", BigInteger.class); + assertEquals(95L, biggie.longValue()); + } + + public void testLegacyFailDoubleToInt() throws Exception + { + _verifyCoerceFail(READER_LEGACY_FAIL, Integer.class, "1.5", "java.lang.Integer"); + _verifyCoerceFail(READER_LEGACY_FAIL, Integer.TYPE, "1.5", "int"); + _verifyCoerceFail(READER_LEGACY_FAIL, IntWrapper.class, "{\"i\":-2.25 }", "int"); + _verifyCoerceFail(READER_LEGACY_FAIL, int[].class, "[ 2.5 ]", "element of `int[]`"); + } + + public void testLegacyFailDoubleToLong() throws Exception + { + _verifyCoerceFail(READER_LEGACY_FAIL, Long.class, "0.5"); + _verifyCoerceFail(READER_LEGACY_FAIL, Long.TYPE, "-2.5"); + _verifyCoerceFail(READER_LEGACY_FAIL, LongWrapper.class, "{\"l\": 7.7 }"); + _verifyCoerceFail(READER_LEGACY_FAIL, long[].class, "[ -1.35 ]", "element of `long[]`"); + } + + 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 ]", "element of `short[]`"); + + _verifyCoerceFail(READER_LEGACY_FAIL, Byte.class, "0.5"); + _verifyCoerceFail(READER_LEGACY_FAIL, Byte.TYPE, "-2.5"); + _verifyCoerceFail(READER_LEGACY_FAIL, byte[].class, "[ -1.35 ]", "element of `byte[]`"); + + _verifyCoerceFail(READER_LEGACY_FAIL, BigInteger.class, "25236.256"); + + _verifyCoerceFail(READER_LEGACY_FAIL, AtomicLong.class, "25236.256"); + } + + /* + /******************************************************** + /* Test methods, legacy, correct exception type + /******************************************************** + */ + + // [databind#2804] + public void testLegacyFail2804() throws Exception + { + _testLegacyFail2804("5.5", Integer.class); + _testLegacyFail2804("5.0", Long.class); + _testLegacyFail2804("1234567890123456789.0", BigInteger.class); + _testLegacyFail2804("[4, 5.5, 6]", "5.5", + new TypeReference>() {}); + _testLegacyFail2804("{\"key1\": 4, \"key2\": 5.5}", "5.5", + new TypeReference>() {}); + } + + private void _testLegacyFail2804(String value, Class type) throws Exception { + _testLegacyFail2804(value, DEFAULT_MAPPER.constructType(type), value); + } + + private void _testLegacyFail2804(String doc, String probValue, + TypeReference type) throws Exception { + _testLegacyFail2804(doc, DEFAULT_MAPPER.constructType(type), probValue); + } + + private void _testLegacyFail2804(String doc, JavaType targetType, + String probValue) throws Exception { + try { + READER_LEGACY_FAIL.forType(targetType).readValue(doc); + fail("Should not pass"); + } catch (InvalidFormatException ex) { + verifyException(ex, probValue); + } catch (MismatchedInputException ex) { + fail("Should get subtype, got: "+ex); + } + } + + /* + /******************************************************** + /* Test methods, CoerceConfig, to null + /******************************************************** + */ + + public void testCoerceConfigFloatToNull() throws Exception + { + assertNull(MAPPER_TO_NULL.readValue("1.5", Integer.class)); + // `null` not possible for primitives, must use empty (aka default) value + assertEquals(Integer.valueOf(0), MAPPER_TO_NULL.readValue("1.5", Integer.TYPE)); + { + IntWrapper w = MAPPER_TO_NULL.readValue( "{\"i\":-2.25 }", IntWrapper.class); + assertEquals(0, w.i); + int[] ints = MAPPER_TO_NULL.readValue("[ 2.5 ]", int[].class); + assertEquals(1, ints.length); + assertEquals(0, ints[0]); + } + + assertNull(MAPPER_TO_NULL.readValue("2.5", Long.class)); + assertEquals(Long.valueOf(0L), MAPPER_TO_NULL.readValue("-4.25", Long.TYPE)); + { + LongWrapper w = MAPPER_TO_NULL.readValue( "{\"l\":-2.25 }", LongWrapper.class); + assertEquals(0L, w.l); + long[] l = MAPPER_TO_NULL.readValue("[ 2.5 ]", long[].class); + assertEquals(1, l.length); + assertEquals(0L, l[0]); + } + + assertNull(MAPPER_TO_NULL.readValue("2.5", Short.class)); + assertEquals(Short.valueOf((short) 0), MAPPER_TO_NULL.readValue("-4.25", Short.TYPE)); + { + short[] s = MAPPER_TO_NULL.readValue("[ 2.5 ]", short[].class); + assertEquals(1, s.length); + assertEquals((short) 0, s[0]); + } + + assertNull(MAPPER_TO_NULL.readValue("2.5", Byte.class)); + assertEquals(Byte.valueOf((byte) 0), MAPPER_TO_NULL.readValue("-4.25", Byte.TYPE)); + { + byte[] arr = MAPPER_TO_NULL.readValue("[ 2.5 ]", byte[].class); + assertEquals(1, arr.length); + assertEquals((byte) 0, arr[0]); + } + + assertNull(MAPPER_TO_NULL.readValue("2.5", BigInteger.class)); + { + BigInteger[] arr = MAPPER_TO_NULL.readValue("[ 2.5 ]", BigInteger[].class); + assertEquals(1, arr.length); + assertNull(arr[0]); + } + } + + /* + /******************************************************** + /* Test methods, CoerceConfig, to empty + /******************************************************** + */ + + public void testCoerceConfigFloatToEmpty() throws Exception + { + assertEquals(Integer.valueOf(0), MAPPER_TO_EMPTY.readValue("1.2", Integer.class)); + assertEquals(Integer.valueOf(0), MAPPER_TO_EMPTY.readValue("1.5", Integer.TYPE)); + { + IntWrapper w = MAPPER_TO_EMPTY.readValue( "{\"i\":-2.25 }", IntWrapper.class); + assertEquals(0, w.i); + int[] ints = MAPPER_TO_EMPTY.readValue("[ 2.5 ]", int[].class); + assertEquals(1, ints.length); + assertEquals(0, ints[0]); + } + + assertEquals(Long.valueOf(0), MAPPER_TO_EMPTY.readValue("1.2", Long.class)); + assertEquals(Long.valueOf(0), MAPPER_TO_EMPTY.readValue("1.5", Long.TYPE)); + { + LongWrapper w = MAPPER_TO_EMPTY.readValue( "{\"l\":-2.25 }", LongWrapper.class); + assertEquals(0L, w.l); + long[] l = MAPPER_TO_EMPTY.readValue("[ 2.5 ]", long[].class); + assertEquals(1, l.length); + assertEquals(0L, l[0]); + } + + assertEquals(Short.valueOf((short)0), MAPPER_TO_EMPTY.readValue("1.2", Short.class)); + assertEquals(Short.valueOf((short) 0), MAPPER_TO_EMPTY.readValue("1.5", Short.TYPE)); + + assertEquals(Byte.valueOf((byte)0), MAPPER_TO_EMPTY.readValue("1.2", Byte.class)); + assertEquals(Byte.valueOf((byte) 0), MAPPER_TO_EMPTY.readValue("1.5", Byte.TYPE)); + + assertEquals(BigInteger.valueOf(0L), MAPPER_TO_EMPTY.readValue("124.5", BigInteger.class)); + } + + /* + /******************************************************** + /* Test methods, CoerceConfig, coerce + /******************************************************** + */ + + public void testCoerceConfigFloatSuccess() throws Exception + { + assertEquals(Integer.valueOf(1), MAPPER_TRY_CONVERT.readValue("1.2", Integer.class)); + assertEquals(Integer.valueOf(3), MAPPER_TRY_CONVERT.readValue("3.4", Integer.TYPE)); + { + IntWrapper w = MAPPER_TRY_CONVERT.readValue( "{\"i\":-2.25 }", IntWrapper.class); + assertEquals(-2, w.i); + int[] ints = MAPPER_TRY_CONVERT.readValue("[ 22.10 ]", int[].class); + assertEquals(1, ints.length); + assertEquals(22, ints[0]); + } + + assertEquals(Long.valueOf(1), MAPPER_TRY_CONVERT.readValue("1.2", Long.class)); + assertEquals(Long.valueOf(1), MAPPER_TRY_CONVERT.readValue("1.5", Long.TYPE)); + { + LongWrapper w = MAPPER_TRY_CONVERT.readValue( "{\"l\":-2.25 }", LongWrapper.class); + assertEquals(-2L, w.l); + long[] l = MAPPER_TRY_CONVERT.readValue("[ 2.2 ]", long[].class); + assertEquals(1, l.length); + assertEquals(2L, l[0]); + } + + assertEquals(Short.valueOf((short)1), MAPPER_TRY_CONVERT.readValue("1.2", Short.class)); + assertEquals(Short.valueOf((short) 19), MAPPER_TRY_CONVERT.readValue("19.2", Short.TYPE)); + + assertEquals(Byte.valueOf((byte)1), MAPPER_TRY_CONVERT.readValue("1.2", Byte.class)); + assertEquals(Byte.valueOf((byte) 1), MAPPER_TRY_CONVERT.readValue("1.5", Byte.TYPE)); + + assertEquals(BigInteger.valueOf(124L), MAPPER_TRY_CONVERT.readValue("124.2", BigInteger.class)); + } + + /* + /******************************************************** + /* Test methods, CoerceConfig, fail + /******************************************************** + */ + + public void testCoerceConfigFailFromFloat() throws Exception + { + _verifyCoerceFail(MAPPER_TO_FAIL, Integer.class, "1.5"); + _verifyCoerceFail(MAPPER_TO_FAIL, Integer.TYPE, "1.5"); + _verifyCoerceFail(MAPPER_TO_FAIL, IntWrapper.class, "{\"i\":-2.25 }", "int"); + _verifyCoerceFail(MAPPER_TO_FAIL, int[].class, "[ 2.5 ]", "element of `int[]`"); + + _verifyCoerceFail(MAPPER_TO_FAIL, Long.class, "0.5"); + _verifyCoerceFail(MAPPER_TO_FAIL, Long.TYPE, "-2.5"); + _verifyCoerceFail(MAPPER_TO_FAIL, LongWrapper.class, "{\"l\": 7.7 }"); + _verifyCoerceFail(MAPPER_TO_FAIL, long[].class, "[ -1.35 ]", "element of `long[]`"); + + _verifyCoerceFail(MAPPER_TO_FAIL, Short.class, "0.5"); + _verifyCoerceFail(MAPPER_TO_FAIL, Short.TYPE, "-2.5"); + _verifyCoerceFail(MAPPER_TO_FAIL, short[].class, "[ -1.35 ]", "element of `short[]`"); + + _verifyCoerceFail(MAPPER_TO_FAIL, Byte.class, "0.5"); + _verifyCoerceFail(MAPPER_TO_FAIL, Byte.TYPE, "-2.5"); + _verifyCoerceFail(MAPPER_TO_FAIL, byte[].class, "[ -1.35 ]", "element of `byte[]`"); + + _verifyCoerceFail(MAPPER_TO_FAIL, BigInteger.class, "25236.256"); + } + + /* + /******************************************************** + /* Helper methods + /******************************************************** + */ + + private void _verifyCoerceFail(ObjectMapper m, Class targetType, + String doc) throws Exception + { + _verifyCoerceFail(m.reader(), targetType, doc, targetType.getName()); + } + + private void _verifyCoerceFail(ObjectMapper m, Class targetType, + String doc, String targetTypeDesc) throws Exception + { + _verifyCoerceFail(m.reader(), targetType, doc, targetTypeDesc); + } + + private void _verifyCoerceFail(ObjectReader r, Class targetType, + String doc) throws Exception + { + _verifyCoerceFail(r, targetType, doc, targetType.getName()); + } + + private void _verifyCoerceFail(ObjectReader r, Class targetType, + String doc, String targetTypeDesc) throws Exception + { + try { + r.forType(targetType).readValue(doc); + fail("Should not accept Float for "+targetType.getName()+" by default"); + } catch (MismatchedInputException e) { + verifyException(e, "Cannot coerce Floating-point"); + verifyException(e, targetTypeDesc); + } + } +} diff --git a/afterburner/src/test/java/com/fasterxml/jackson/module/afterburner/deser/convert/CoerceJDKScalarsTest.java b/afterburner/src/test/java/com/fasterxml/jackson/module/afterburner/deser/convert/CoerceJDKScalarsTest.java new file mode 100644 index 00000000..1e4d43dc --- /dev/null +++ b/afterburner/src/test/java/com/fasterxml/jackson/module/afterburner/deser/convert/CoerceJDKScalarsTest.java @@ -0,0 +1,245 @@ +package com.fasterxml.jackson.module.afterburner.deser.convert; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.StringReader; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.concurrent.atomic.AtomicBoolean; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.exc.MismatchedInputException; +import com.fasterxml.jackson.module.afterburner.AfterburnerTestBase; + +// Copied from "jackson-databind" as of 2.12.1 +public class CoerceJDKScalarsTest extends AfterburnerTestBase +{ + static class BooleanPOJO { + public Boolean value; + } + + static class BooleanWrapper { + public Boolean wrapper; + public boolean primitive; + + protected Boolean ctor; + + @JsonCreator + public BooleanWrapper(@JsonProperty("ctor") Boolean foo) { + ctor = foo; + } + } + + private final ObjectMapper COERCING_MAPPER = mapperBuilder() + .enable(MapperFeature.ALLOW_COERCION_OF_SCALARS) + .build(); + + private final ObjectMapper NOT_COERCING_MAPPER = mapperBuilder() + .disable(MapperFeature.ALLOW_COERCION_OF_SCALARS) + .build(); + + /* + /********************************************************** + /* Unit tests: coercion from empty String + /********************************************************** + */ + + public void testNullValueFromEmpty() throws Exception + { + // wrappers accept `null` fine + _verifyNullOkFromEmpty(Boolean.class, null); + // but primitives require non-null + _verifyNullOkFromEmpty(Boolean.TYPE, Boolean.FALSE); + + _verifyNullOkFromEmpty(Byte.class, null); + _verifyNullOkFromEmpty(Byte.TYPE, Byte.valueOf((byte) 0)); + _verifyNullOkFromEmpty(Short.class, null); + _verifyNullOkFromEmpty(Short.TYPE, Short.valueOf((short) 0)); + _verifyNullOkFromEmpty(Character.class, null); + _verifyNullOkFromEmpty(Character.TYPE, Character.valueOf((char) 0)); + _verifyNullOkFromEmpty(Integer.class, null); + _verifyNullOkFromEmpty(Integer.TYPE, Integer.valueOf(0)); + _verifyNullOkFromEmpty(Long.class, null); + _verifyNullOkFromEmpty(Long.TYPE, Long.valueOf(0L)); + _verifyNullOkFromEmpty(Float.class, null); + _verifyNullOkFromEmpty(Float.TYPE, Float.valueOf(0.0f)); + _verifyNullOkFromEmpty(Double.class, null); + _verifyNullOkFromEmpty(Double.TYPE, Double.valueOf(0.0)); + + _verifyNullOkFromEmpty(BigInteger.class, null); + _verifyNullOkFromEmpty(BigDecimal.class, null); + _verifyNullOkFromEmpty(AtomicBoolean.class, null); + } + + private void _verifyNullOkFromEmpty(Class type, Object exp) throws IOException + { + Object result = COERCING_MAPPER.readerFor(type) + .with(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT) + .readValue("\"\""); + if (exp == null) { + assertNull(result); + } else { + assertEquals(exp, result); + } + } + + public void testNullFailFromEmpty() throws Exception + { + _verifyNullFail(Boolean.class); + _verifyNullFail(Boolean.TYPE); + + _verifyNullFail(Byte.class); + _verifyNullFail(Byte.TYPE); + _verifyNullFail(Short.class); + _verifyNullFail(Short.TYPE); + _verifyNullFail(Character.class); + _verifyNullFail(Character.TYPE); + _verifyNullFail(Integer.class); + _verifyNullFail(Integer.TYPE); + _verifyNullFail(Long.class); + _verifyNullFail(Long.TYPE); + _verifyNullFail(Float.class); + _verifyNullFail(Float.TYPE); + _verifyNullFail(Double.class); + _verifyNullFail(Double.TYPE); + + _verifyNullFail(BigInteger.class); + _verifyNullFail(BigDecimal.class); + _verifyNullFail(AtomicBoolean.class); + } + + private void _verifyNullFail(Class type) throws IOException + { + try { + NOT_COERCING_MAPPER.readerFor(type).readValue("\"\""); + fail("Should have failed for "+type); + } catch (MismatchedInputException e) { + verifyException(e, "Cannot coerce empty String"); + } + } + + /* + /********************************************************** + /* Unit tests: coercion from secondary representations + /********************************************************** + */ + + public void testStringToNumbersCoercionOk() throws Exception + { + _verifyCoerceSuccess(quote("123"), Byte.TYPE, Byte.valueOf((byte) 123)); + _verifyCoerceSuccess(quote("123"), Byte.class, Byte.valueOf((byte) 123)); + _verifyCoerceSuccess(quote("123"), Short.TYPE, Short.valueOf((short) 123)); + _verifyCoerceSuccess(quote("123"), Short.class, Short.valueOf((short) 123)); + _verifyCoerceSuccess(quote("123"), Integer.TYPE, Integer.valueOf(123)); + _verifyCoerceSuccess(quote("123"), Integer.class, Integer.valueOf(123)); + _verifyCoerceSuccess(quote("123"), Long.TYPE, Long.valueOf(123)); + _verifyCoerceSuccess(quote("123"), Long.class, Long.valueOf(123)); + _verifyCoerceSuccess(quote("123.5"), Float.TYPE, Float.valueOf(123.5f)); + _verifyCoerceSuccess(quote("123.5"), Float.class, Float.valueOf(123.5f)); + _verifyCoerceSuccess(quote("123.5"), Double.TYPE, Double.valueOf(123.5)); + _verifyCoerceSuccess(quote("123.5"), Double.class, Double.valueOf(123.5)); + + _verifyCoerceSuccess(quote("123"), BigInteger.class, BigInteger.valueOf(123)); + _verifyCoerceSuccess(quote("123.0"), BigDecimal.class, new BigDecimal("123.0")); + + AtomicBoolean ab = COERCING_MAPPER.readValue(quote("true"), AtomicBoolean.class); + assertNotNull(ab); + assertTrue(ab.get()); + } + + public void testStringCoercionFailInteger() throws Exception + { + _verifyRootStringCoerceFail("123", Byte.TYPE); + _verifyRootStringCoerceFail("123", Byte.class); + _verifyRootStringCoerceFail("123", Short.TYPE); + _verifyRootStringCoerceFail("123", Short.class); + _verifyRootStringCoerceFail("123", Integer.TYPE); + _verifyRootStringCoerceFail("123", Integer.class); + _verifyRootStringCoerceFail("123", Long.TYPE); + _verifyRootStringCoerceFail("123", Long.class); + } + + public void testStringCoercionFailFloat() throws Exception + { + _verifyRootStringCoerceFail("123.5", Float.TYPE); + _verifyRootStringCoerceFail("123.5", Float.class); + _verifyRootStringCoerceFail("123.5", Double.TYPE); + _verifyRootStringCoerceFail("123.5", Double.class); + + _verifyRootStringCoerceFail("123", BigInteger.class); + _verifyRootStringCoerceFail("123.0", BigDecimal.class); + } + + public void testMiscCoercionFail() throws Exception + { + // And then we have coercions from more esoteric types too + + _verifyCoerceFail("65", Character.class, + "Cannot coerce Integer value (65) to `java.lang.Character` value"); + _verifyCoerceFail("65", Character.TYPE, + "Cannot coerce Integer value (65) to `char` value"); + } + + /* + /********************************************************** + /* Helper methods + /********************************************************** + */ + + private void _verifyCoerceSuccess(String input, Class type, Object exp) throws IOException + { + Object result = COERCING_MAPPER.readerFor(type) + .readValue(input); + assertEquals(exp, result); + } + + private void _verifyCoerceFail(String input, Class type, + String... expMatches) throws IOException + { + try { + NOT_COERCING_MAPPER.readerFor(type) + .readValue(input); + fail("Should not have allowed coercion"); + } catch (MismatchedInputException e) { + verifyException(e, expMatches); + } + } + + private void _verifyRootStringCoerceFail(String unquotedValue, Class type) throws IOException + { + // Test failure for root value: for both byte- and char-backed sources: + + final String input = quote(unquotedValue); + try (JsonParser p = NOT_COERCING_MAPPER.createParser(new StringReader(input))) { + _verifyStringCoerceFail(p, unquotedValue, type); + } + final byte[] inputBytes = utf8Bytes(input); + try (JsonParser p = NOT_COERCING_MAPPER.createParser(new ByteArrayInputStream(inputBytes))) { + _verifyStringCoerceFail(p, unquotedValue, type); + } + } + + private void _verifyStringCoerceFail(JsonParser p, + String unquotedValue, Class type) throws IOException + { + try { + NOT_COERCING_MAPPER.readerFor(type) + .readValue(p); + fail("Should not have allowed coercion"); + } catch (MismatchedInputException e) { + verifyException(e, "Cannot coerce "); + verifyException(e, " to `"); + verifyException(e, "` value"); + + assertNotNull(e.getProcessor()); + assertSame(p, e.getProcessor()); + + assertToken(JsonToken.VALUE_STRING, p.currentToken()); + assertEquals(unquotedValue, p.getText()); + } + } +} diff --git a/afterburner/src/test/java/com/fasterxml/jackson/module/afterburner/deser/convert/CoerceStringToIntsTest.java b/afterburner/src/test/java/com/fasterxml/jackson/module/afterburner/deser/convert/CoerceStringToIntsTest.java new file mode 100644 index 00000000..10eddf33 --- /dev/null +++ b/afterburner/src/test/java/com/fasterxml/jackson/module/afterburner/deser/convert/CoerceStringToIntsTest.java @@ -0,0 +1,315 @@ +package com.fasterxml.jackson.module.afterburner.deser.convert; + +import java.math.BigInteger; +import java.util.concurrent.atomic.AtomicLong; + +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.cfg.CoercionAction; +import com.fasterxml.jackson.databind.cfg.CoercionInputShape; +import com.fasterxml.jackson.databind.exc.MismatchedInputException; +import com.fasterxml.jackson.databind.type.LogicalType; +import com.fasterxml.jackson.module.afterburner.AfterburnerTestBase; + +// Copied from "jackson-databind" as of 2.12.1 +public class CoerceStringToIntsTest extends AfterburnerTestBase +{ + private final ObjectMapper DEFAULT_MAPPER = newAfterburnerMapper(); + private final ObjectReader READER_LEGACY_FAIL = mapperBuilder() + .disable(MapperFeature.ALLOW_COERCION_OF_SCALARS) + .build() + .reader(); + + private final ObjectMapper MAPPER_TO_EMPTY; { + MAPPER_TO_EMPTY = newAfterburnerMapper(); + MAPPER_TO_EMPTY.coercionConfigFor(LogicalType.Integer) + .setCoercion(CoercionInputShape.String, CoercionAction.AsEmpty); + } + + private final ObjectMapper MAPPER_TRY_CONVERT; { + MAPPER_TRY_CONVERT = newAfterburnerMapper(); + MAPPER_TRY_CONVERT.coercionConfigFor(LogicalType.Integer) + .setCoercion(CoercionInputShape.String, CoercionAction.TryConvert); + } + + private final ObjectMapper MAPPER_TO_NULL; { + MAPPER_TO_NULL = newAfterburnerMapper(); + MAPPER_TO_NULL.coercionConfigFor(LogicalType.Integer) + .setCoercion(CoercionInputShape.String, CoercionAction.AsNull); + } + + private final ObjectMapper MAPPER_TO_FAIL; { + MAPPER_TO_FAIL = newAfterburnerMapper(); + MAPPER_TO_FAIL.coercionConfigFor(LogicalType.Integer) + .setCoercion(CoercionInputShape.String, CoercionAction.Fail); + } + + /* + /******************************************************** + /* Test methods, defaults (legacy) + /******************************************************** + */ + + public void testLegacyStringToIntCoercion() throws Exception + { + // by default, should be ok + Integer I = DEFAULT_MAPPER.readValue(q("28"), Integer.class); + assertEquals(28, I.intValue()); + { + IntWrapper w = DEFAULT_MAPPER.readValue(a2q("{'i':'37' }"), IntWrapper.class); + assertEquals(37, w.i); + int[] arr = DEFAULT_MAPPER.readValue(a2q("[ '42' ]"), int[].class); + assertEquals(42, arr[0]); + } + + Long L = DEFAULT_MAPPER.readValue(q("39"), Long.class); + assertEquals(39L, L.longValue()); + { + LongWrapper w = DEFAULT_MAPPER.readValue(a2q("{'l':'-13' }"), LongWrapper.class); + assertEquals(-13L, w.l); + long[] arr = DEFAULT_MAPPER.readValue(a2q("[ '0' ]"), long[].class); + assertEquals(0L, arr[0]); + } + + Short S = DEFAULT_MAPPER.readValue(q("42"), Short.class); + assertEquals(42, S.intValue()); + + BigInteger biggie = DEFAULT_MAPPER.readValue(q("95007"), BigInteger.class); + assertEquals(95007, biggie.intValue()); + } + + public void testLegacyFailStringToInt() throws Exception + { + _verifyCoerceFail(READER_LEGACY_FAIL, Integer.class, q("52"), "java.lang.Integer"); + _verifyCoerceFail(READER_LEGACY_FAIL, Integer.TYPE, q("37"), "int"); + _verifyCoerceFail(READER_LEGACY_FAIL, int[].class, "[ \"-128\" ]", "element of `int[]`"); + _verifyCoerceFail(READER_LEGACY_FAIL, IntWrapper.class, "{\"i\":\"19\" }", "int"); + } + + public void testLegacyFailStringToLong() throws Exception + { + _verifyCoerceFail(READER_LEGACY_FAIL, Long.class, q("55")); + _verifyCoerceFail(READER_LEGACY_FAIL, Long.TYPE, q("-25")); + _verifyCoerceFail(READER_LEGACY_FAIL, long[].class, "[ \"136\" ]", "element of `long[]`"); + _verifyCoerceFail(READER_LEGACY_FAIL, LongWrapper.class, "{\"l\": \"77\" }"); + } + + public void testLegacyFailStringToOther() throws Exception + { + _verifyCoerceFail(READER_LEGACY_FAIL, Short.class, q("50")); + _verifyCoerceFail(READER_LEGACY_FAIL, Short.TYPE, q("-255")); + _verifyCoerceFail(READER_LEGACY_FAIL, short[].class, "[ \"-126\" ]", "element of `short[]`"); + + _verifyCoerceFail(READER_LEGACY_FAIL, Byte.class, q("60")); + _verifyCoerceFail(READER_LEGACY_FAIL, Byte.TYPE, q("-25")); + _verifyCoerceFail(READER_LEGACY_FAIL, byte[].class, "[ \"-13\" ]", "element of `byte[]`"); + + _verifyCoerceFail(READER_LEGACY_FAIL, BigInteger.class, q("25236")); + + _verifyCoerceFail(READER_LEGACY_FAIL, AtomicLong.class, q("25236")); + } + + /* + /******************************************************** + /* Test methods, CoerceConfig, to null + /******************************************************** + */ + + public void testCoerceConfigStringToNullInt() throws Exception + { + assertNull(MAPPER_TO_NULL.readValue(q("155"), Integer.class)); + // `null` not possible for primitives, must use empty (aka default) value + assertEquals(Integer.valueOf(0), MAPPER_TO_NULL.readValue(q("-178"), Integer.TYPE)); + { + IntWrapper w = MAPPER_TO_NULL.readValue( "{\"i\":\"-225\" }", IntWrapper.class); + assertEquals(0, w.i); + int[] ints = MAPPER_TO_NULL.readValue("[ \"26\" ]", int[].class); + assertEquals(1, ints.length); + assertEquals(0, ints[0]); + } + } + + public void testCoerceConfigStringToNullLong() throws Exception + { + assertNull(MAPPER_TO_NULL.readValue(q("25"), Long.class)); + assertEquals(Long.valueOf(0L), MAPPER_TO_NULL.readValue(q("-425"), Long.TYPE)); + { + LongWrapper w = MAPPER_TO_NULL.readValue( "{\"l\":\"-225\" }", LongWrapper.class); + assertEquals(0L, w.l); + long[] l = MAPPER_TO_NULL.readValue("[ \"190\" ]", long[].class); + assertEquals(1, l.length); + assertEquals(0L, l[0]); + } + } + + public void testCoerceConfigStringToNullOther() throws Exception + { + assertNull(MAPPER_TO_NULL.readValue(q("25"), Short.class)); + assertEquals(Short.valueOf((short) 0), MAPPER_TO_NULL.readValue(q("-425"), Short.TYPE)); + { + short[] s = MAPPER_TO_NULL.readValue("[ \"25\" ]", short[].class); + assertEquals(1, s.length); + assertEquals((short) 0, s[0]); + } + + assertNull(MAPPER_TO_NULL.readValue(q("29"), Byte.class)); + assertEquals(Byte.valueOf((byte) 0), MAPPER_TO_NULL.readValue(q("-425"), Byte.TYPE)); + { + byte[] arr = MAPPER_TO_NULL.readValue("[ \"25\" ]", byte[].class); + assertEquals(1, arr.length); + assertEquals((byte) 0, arr[0]); + } + + assertNull(MAPPER_TO_NULL.readValue(q("25000000"), BigInteger.class)); + { + BigInteger[] arr = MAPPER_TO_NULL.readValue("[ \"25\" ]", BigInteger[].class); + assertEquals(1, arr.length); + assertNull(arr[0]); + } + } + + /* + /******************************************************** + /* Test methods, CoerceConfig, to empty + /******************************************************** + */ + + public void testCoerceConfigStringToEmptyInt() throws Exception + { + assertEquals(Integer.valueOf(0), MAPPER_TO_EMPTY.readValue(q("12"), Integer.class)); + assertEquals(Integer.valueOf(0), MAPPER_TO_EMPTY.readValue(q("15"), Integer.TYPE)); + { + IntWrapper w = MAPPER_TO_EMPTY.readValue( "{\"i\":\"-225\" }", IntWrapper.class); + assertEquals(0, w.i); + int[] ints = MAPPER_TO_EMPTY.readValue("[ \"25\" ]", int[].class); + assertEquals(1, ints.length); + assertEquals(0, ints[0]); + } + } + + public void testCoerceConfigStringToEmptyLong() throws Exception + { + assertEquals(Long.valueOf(0), MAPPER_TO_EMPTY.readValue(q("12"), Long.class)); + assertEquals(Long.valueOf(0), MAPPER_TO_EMPTY.readValue(q("99"), Long.TYPE)); + { + LongWrapper w = MAPPER_TO_EMPTY.readValue( "{\"l\":\"-225\" }", LongWrapper.class); + assertEquals(0L, w.l); + long[] l = MAPPER_TO_EMPTY.readValue("[ \"26\" ]", long[].class); + assertEquals(1, l.length); + assertEquals(0L, l[0]); + } + } + + public void testCoerceConfigStringToEmptyOther() throws Exception + { + assertEquals(Short.valueOf((short)0), MAPPER_TO_EMPTY.readValue(q("12"), Short.class)); + assertEquals(Short.valueOf((short) 0), MAPPER_TO_EMPTY.readValue(q("999"), Short.TYPE)); + + assertEquals(Byte.valueOf((byte)0), MAPPER_TO_EMPTY.readValue(q("12"), Byte.class)); + assertEquals(Byte.valueOf((byte) 0), MAPPER_TO_EMPTY.readValue(q("123"), Byte.TYPE)); + + assertEquals(BigInteger.valueOf(0L), MAPPER_TO_EMPTY.readValue(q("1234"), BigInteger.class)); + } + + /* + /******************************************************** + /* Test methods, CoerceConfig, coerce + /******************************************************** + */ + + public void testCoerceConfigStringConvert() throws Exception + { + assertEquals(Integer.valueOf(12), MAPPER_TRY_CONVERT.readValue(q("12"), Integer.class)); + assertEquals(Integer.valueOf(34), MAPPER_TRY_CONVERT.readValue(q("34"), Integer.TYPE)); + { + IntWrapper w = MAPPER_TRY_CONVERT.readValue( "{\"i\":\"-225\" }", IntWrapper.class); + assertEquals(-225, w.i); + int[] ints = MAPPER_TRY_CONVERT.readValue("[ \"2210\" ]", int[].class); + assertEquals(1, ints.length); + assertEquals(2210, ints[0]); + } + + assertEquals(Long.valueOf(34), MAPPER_TRY_CONVERT.readValue(q("34"), Long.class)); + assertEquals(Long.valueOf(534), MAPPER_TRY_CONVERT.readValue(q("534"), Long.TYPE)); + { + LongWrapper w = MAPPER_TRY_CONVERT.readValue( "{\"l\":\"-225\" }", LongWrapper.class); + assertEquals(-225L, w.l); + long[] l = MAPPER_TRY_CONVERT.readValue("[ \"22\" ]", long[].class); + assertEquals(1, l.length); + assertEquals(22L, l[0]); + } + + assertEquals(Short.valueOf((short)12), MAPPER_TRY_CONVERT.readValue(q("12"), Short.class)); + assertEquals(Short.valueOf((short) 344), MAPPER_TRY_CONVERT.readValue(q("344"), Short.TYPE)); + + assertEquals(Byte.valueOf((byte)12), MAPPER_TRY_CONVERT.readValue(q("12"), Byte.class)); + assertEquals(Byte.valueOf((byte) -99), MAPPER_TRY_CONVERT.readValue(q("-99"), Byte.TYPE)); + + assertEquals(BigInteger.valueOf(1242L), MAPPER_TRY_CONVERT.readValue(q("1242"), BigInteger.class)); + } + + /* + /******************************************************** + /* Test methods, CoerceConfig, fail + /******************************************************** + */ + + public void testCoerceConfigFailFromString() throws Exception + { + _verifyCoerceFail(MAPPER_TO_FAIL, Integer.class, q("15")); + _verifyCoerceFail(MAPPER_TO_FAIL, Integer.TYPE, q("15")); + _verifyCoerceFail(MAPPER_TO_FAIL, int[].class, "[ \"256\" ]", "element of `int[]`"); + + _verifyCoerceFail(MAPPER_TO_FAIL, Long.class, q("738")); + _verifyCoerceFail(MAPPER_TO_FAIL, Long.TYPE, q("-99")); + _verifyCoerceFail(MAPPER_TO_FAIL, long[].class, "[ \"-135\" ]", "element of `long[]`"); + + _verifyCoerceFail(MAPPER_TO_FAIL, Short.class, q("-19")); + _verifyCoerceFail(MAPPER_TO_FAIL, Short.TYPE, q("25")); + _verifyCoerceFail(MAPPER_TO_FAIL, short[].class, "[ \"-135\" ]", "element of `short[]`"); + + _verifyCoerceFail(MAPPER_TO_FAIL, Byte.class, q("15")); + _verifyCoerceFail(MAPPER_TO_FAIL, Byte.TYPE, q("-25")); + _verifyCoerceFail(MAPPER_TO_FAIL, byte[].class, "[ \"-1379\" ]", "element of `byte[]`"); + + _verifyCoerceFail(MAPPER_TO_FAIL, BigInteger.class, q("25236256")); + + _verifyCoerceFail(MAPPER_TO_FAIL, IntWrapper.class, "{\"i\":\"-225\" }", "int"); + _verifyCoerceFail(MAPPER_TO_FAIL, LongWrapper.class, "{\"l\": \"77\" }"); + } + + /* + /******************************************************** + /* Helper methods + /******************************************************** + */ + + private void _verifyCoerceFail(ObjectMapper m, Class targetType, + String doc) throws Exception + { + _verifyCoerceFail(m.reader(), targetType, doc, targetType.getName()); + } + + private void _verifyCoerceFail(ObjectMapper m, Class targetType, + String doc, String targetTypeDesc) throws Exception + { + _verifyCoerceFail(m.reader(), targetType, doc, targetTypeDesc); + } + + private void _verifyCoerceFail(ObjectReader r, Class targetType, + String doc) throws Exception + { + _verifyCoerceFail(r, targetType, doc, targetType.getName()); + } + + private void _verifyCoerceFail(ObjectReader r, Class targetType, + String doc, String targetTypeDesc) throws Exception + { + try { + r.forType(targetType).readValue(doc); + fail("Should not accept String for "+targetType.getName()+" by default"); + } catch (MismatchedInputException e) { + verifyException(e, "Cannot coerce String"); + verifyException(e, targetTypeDesc); + } + } +} diff --git a/afterburner/src/test/java/com/fasterxml/jackson/module/afterburner/deser/TestFailOnPrimitiveFromNullDeserialization.java b/afterburner/src/test/java/com/fasterxml/jackson/module/afterburner/deser/convert/TestFailOnPrimitiveFromNullDeserialization.java similarity index 92% rename from afterburner/src/test/java/com/fasterxml/jackson/module/afterburner/deser/TestFailOnPrimitiveFromNullDeserialization.java rename to afterburner/src/test/java/com/fasterxml/jackson/module/afterburner/deser/convert/TestFailOnPrimitiveFromNullDeserialization.java index 07952225..c27ff2aa 100644 --- a/afterburner/src/test/java/com/fasterxml/jackson/module/afterburner/deser/TestFailOnPrimitiveFromNullDeserialization.java +++ b/afterburner/src/test/java/com/fasterxml/jackson/module/afterburner/deser/convert/TestFailOnPrimitiveFromNullDeserialization.java @@ -1,4 +1,4 @@ -package com.fasterxml.jackson.module.afterburner.deser; +package com.fasterxml.jackson.module.afterburner.deser.convert; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; @@ -51,13 +51,13 @@ public void testFailPrimitiveFromNull() throws Exception FAIL_ON_NULL_MAPPER.readValue(BEAN_WITH_NULL_VALUE, IntBean.class); fail(); } catch (MismatchedInputException e) { - verifyException(e, "Cannot map `null` into type int"); + verifyException(e, "Cannot coerce `null` to `int` value"); } try { FAIL_ON_NULL_MAPPER.readValue(BEAN_WITH_NULL_VALUE, LongBean.class); fail(); } catch (MismatchedInputException e) { - verifyException(e, "Cannot map `null` into type long"); + verifyException(e, "Cannot coerce `null` to `long` value"); } try { FAIL_ON_NULL_MAPPER.readValue(BEAN_WITH_NULL_VALUE, BooleanBean.class); diff --git a/afterburner/src/test/java/com/fasterxml/jackson/module/afterburner/deser/filter/IgnoreCreatorProp1317Test.java b/afterburner/src/test/java/com/fasterxml/jackson/module/afterburner/deser/filter/IgnoreCreatorProp1317Test.java index 07ff770f..dd2fa103 100644 --- a/afterburner/src/test/java/com/fasterxml/jackson/module/afterburner/deser/filter/IgnoreCreatorProp1317Test.java +++ b/afterburner/src/test/java/com/fasterxml/jackson/module/afterburner/deser/filter/IgnoreCreatorProp1317Test.java @@ -41,10 +41,11 @@ public void setNotIgnore(String notIgnore) { } } + private final ObjectMapper MAPPER = newAfterburnerMapper(); + public void testThatJsonIgnoreWorksWithConstructorProperties() throws Exception { - ObjectMapper om = objectMapper(); Testing testing = new Testing("shouldBeIgnored", "notIgnore"); - String json = om.writeValueAsString(testing); + String json = MAPPER.writeValueAsString(testing); // System.out.println(json); assertFalse(json.contains("shouldBeIgnored")); } diff --git a/afterburner/src/test/java/com/fasterxml/jackson/module/afterburner/deser/jdk/JDKScalarsDeserTest.java b/afterburner/src/test/java/com/fasterxml/jackson/module/afterburner/deser/jdk/JDKScalarsDeserTest.java index 75f61ef3..37e7a58d 100644 --- a/afterburner/src/test/java/com/fasterxml/jackson/module/afterburner/deser/jdk/JDKScalarsDeserTest.java +++ b/afterburner/src/test/java/com/fasterxml/jackson/module/afterburner/deser/jdk/JDKScalarsDeserTest.java @@ -2,13 +2,21 @@ import java.io.*; import java.lang.reflect.Array; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; import org.junit.Assert; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; + import com.fasterxml.jackson.core.*; + import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.JsonMappingException.Reference; +import com.fasterxml.jackson.databind.exc.MismatchedInputException; +import com.fasterxml.jackson.databind.util.ClassUtil; import com.fasterxml.jackson.module.afterburner.AfterburnerTestBase; /** @@ -46,7 +54,7 @@ static class LongBean { long _v; void setV(long v) { _v = v; } } - + final static class DoubleBean { double _v; void setV(double v) { _v = v; } @@ -103,7 +111,20 @@ static class WrappersBean public Double doubleValue; } - private final ObjectMapper MAPPER = newObjectMapper(); + // [databind#2101] + static class PrimitiveCreatorBean + { + @JsonCreator + public PrimitiveCreatorBean(@JsonProperty(value="a",required=true) int a, + @JsonProperty(value="b",required=true) int b) { } + } + + // [databind#2197] + static class VoidBean { + public Void value; + } + + private final ObjectMapper MAPPER = newAfterburnerMapper(); /* /********************************************************** @@ -141,39 +162,6 @@ public void testBooleanWrapper() throws Exception assertEquals(Boolean.TRUE, result); result = MAPPER.readValue("false", Boolean.class); assertEquals(Boolean.FALSE, result); - - // should accept ints too, (0 == false, otherwise true) - result = MAPPER.readValue("0", Boolean.class); - assertEquals(Boolean.FALSE, result); - result = MAPPER.readValue("1", Boolean.class); - assertEquals(Boolean.TRUE, result); - } - - // Test for verifying that Long values are coerced to boolean correctly as well - public void testLongToBoolean() throws Exception - { - long value = 1L + Integer.MAX_VALUE; - BooleanWrapper b = MAPPER.readValue("{\"primitive\" : "+value+", \"wrapper\":"+value+", \"ctor\":"+value+"}", - BooleanWrapper.class); - assertEquals(Boolean.TRUE, b.wrapper); - assertTrue(b.primitive); - assertEquals(Boolean.TRUE, b.ctor); - - // but ensure we can also get `false` - b = MAPPER.readValue("{\"primitive\" : 0 , \"wrapper\":0, \"ctor\":0}", - BooleanWrapper.class); - assertEquals(Boolean.FALSE, b.wrapper); - assertFalse(b.primitive); - assertEquals(Boolean.FALSE, b.ctor); - - boolean[] boo = MAPPER.readValue("[ 0, 15, \"\", \"false\", \"True\" ]", - boolean[].class); - assertEquals(5, boo.length); - assertFalse(boo[0]); - assertTrue(boo[1]); - assertFalse(boo[2]); - assertFalse(boo[3]); - assertTrue(boo[4]); } /* @@ -211,29 +199,31 @@ public void testShortWrapper() throws Exception public void testCharacterWrapper() throws Exception { // First: canonical value is 1-char string - Character result = MAPPER.readValue("\"a\"", Character.class); - assertEquals(Character.valueOf('a'), result); + assertEquals(Character.valueOf('a'), MAPPER.readValue(quote("a"), Character.class)); // But can also pass in ascii code - result = MAPPER.readValue(" "+((int) 'X'), Character.class); + Character result = MAPPER.readValue(" "+((int) 'X'), Character.class); assertEquals(Character.valueOf('X'), result); + + // 22-Jun-2020, tatu: one special case turns out to be white space; + // need to avoid considering it "blank" value + assertEquals(Character.valueOf(' '), MAPPER.readValue(quote(" "), Character.class)); final CharacterWrapperBean wrapper = MAPPER.readValue("{\"v\":null}", CharacterWrapperBean.class); assertNotNull(wrapper); assertNull(wrapper.getV()); - - final ObjectMapper mapper = new ObjectMapper(); - mapper.enable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES); + try { - mapper.readValue("{\"v\":null}", CharacterBean.class); + MAPPER.readerFor(CharacterBean.class) + .with(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES) + .readValue("{\"v\":null}"); fail("Attempting to deserialize a 'null' JSON reference into a 'char' property did not throw an exception"); - } catch (JsonMappingException e) { + } catch (MismatchedInputException e) { verifyException(e, "cannot map `null`"); - //Exception thrown as required } - - mapper.disable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES); - final CharacterBean charBean = MAPPER.readValue("{\"v\":null}", CharacterBean.class); + final CharacterBean charBean = MAPPER.readerFor(CharacterBean.class) + .without(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES) + .readValue("{\"v\":null}"); assertNotNull(wrapper); assertEquals('\u0000', charBean.getV()); } @@ -266,42 +256,8 @@ public void testIntPrimitive() throws Exception assertNotNull(array); assertEquals(1, array.length); assertEquals(0, array[0]); - - // [databind#381] - final ObjectMapper mapper = new ObjectMapper(); - mapper.disable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS); - try { - mapper.readValue("{\"v\":[3]}", IntBean.class); - fail("Did not throw exception when reading a value from a single value array with the UNWRAP_SINGLE_VALUE_ARRAYS feature disabled"); - } catch (JsonMappingException exp) { - //Correctly threw exception - } - - mapper.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS); - - result = mapper.readValue("{\"v\":[3]}", IntBean.class); - assertEquals(3, result._v); - - result = mapper.readValue("[{\"v\":[3]}]", IntBean.class); - assertEquals(3, result._v); - - try { - mapper.readValue("[{\"v\":[3,3]}]", IntBean.class); - fail("Did not throw exception while reading a value from a multi value array with UNWRAP_SINGLE_VALUE_ARRAY feature enabled"); - } catch (JsonMappingException exp) { - //threw exception as required - } - - result = mapper.readValue("{\"v\":[null]}", IntBean.class); - assertNotNull(result); - assertEquals(0, result._v); - - array = mapper.readValue("[ [ null ] ]", int[].class); - assertNotNull(array); - assertEquals(1, array.length); - assertEquals(0, array[0]); } - + public void testLongWrapper() throws Exception { Long result = MAPPER.readValue("12345678901", Long.class); @@ -329,40 +285,6 @@ public void testLongPrimitive() throws Exception assertNotNull(array); assertEquals(1, array.length); assertEquals(0, array[0]); - - // [databind#381] - final ObjectMapper mapper = new ObjectMapper(); - mapper.disable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS); - try { - mapper.readValue("{\"v\":[3]}", LongBean.class); - fail("Did not throw exception when reading a value from a single value array with the UNWRAP_SINGLE_VALUE_ARRAYS feature disabled"); - } catch (JsonMappingException exp) { - //Correctly threw exception - } - - mapper.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS); - - result = mapper.readValue("{\"v\":[3]}", LongBean.class); - assertEquals(3, result._v); - - result = mapper.readValue("[{\"v\":[3]}]", LongBean.class); - assertEquals(3, result._v); - - try { - mapper.readValue("[{\"v\":[3,3]}]", LongBean.class); - fail("Did not throw exception while reading a value from a multi value array with UNWRAP_SINGLE_VALUE_ARRAY feature enabled"); - } catch (JsonMappingException exp) { - //threw exception as required - } - - result = mapper.readValue("{\"v\":[null]}", LongBean.class); - assertNotNull(result); - assertEquals(0, result._v); - - array = mapper.readValue("[ [ null ] ]", long[].class); - assertNotNull(array); - assertEquals(1, array.length); - assertEquals(0, array[0]); } /** @@ -442,7 +364,7 @@ public void testDoubleWrapper() throws Exception // First, as regular double value if (NAN_STRING != str) { result = MAPPER.readValue(str, Double.class); - assertEquals(exp, result); + assertEquals(exp, result); } // and then as coerced String: result = MAPPER.readValue(" \""+str+"\"", Double.class); @@ -450,73 +372,6 @@ public void testDoubleWrapper() throws Exception } } - public void testDoubleAsArray() throws Exception - { - final ObjectMapper mapper = new ObjectMapper(); - mapper.disable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS); - final double value = 0.016; - try { - mapper.readValue("{\"v\":[" + value + "]}", DoubleBean.class); - fail("Did not throw exception when reading a value from a single value array with the UNWRAP_SINGLE_VALUE_ARRAYS feature disabled"); - } catch (JsonMappingException exp) { - //Correctly threw exception - } - - mapper.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS); - - DoubleBean result = mapper.readValue("{\"v\":[" + value + "]}", - DoubleBean.class); - assertEquals(value, result._v); - - result = mapper.readValue("[{\"v\":[" + value + "]}]", DoubleBean.class); - assertEquals(value, result._v); - - try { - mapper.readValue("[{\"v\":[" + value + "," + value + "]}]", DoubleBean.class); - fail("Did not throw exception while reading a value from a multi value array with UNWRAP_SINGLE_VALUE_ARRAY feature enabled"); - } catch (JsonMappingException exp) { - //threw exception as required - } - - result = mapper.readValue("{\"v\":[null]}", DoubleBean.class); - assertNotNull(result); - assertEquals(0d, result._v); - - double[] array = mapper.readValue("[ [ null ] ]", double[].class); - assertNotNull(array); - assertEquals(1, array.length); - assertEquals(0d, array[0]); - } - - public void testDoublePrimitiveNonNumeric() throws Exception - { - // first, simple case: - // bit tricky with binary fps but... - double value = Double.POSITIVE_INFINITY; - DoubleBean result = MAPPER.readValue("{\"v\":\""+value+"\"}", DoubleBean.class); - assertEquals(value, result._v); - - // should work with arrays too.. - double[] array = MAPPER.readValue("[ \"Infinity\" ]", double[].class); - assertNotNull(array); - assertEquals(1, array.length); - assertEquals(Double.POSITIVE_INFINITY, array[0]); - } - - public void testFloatPrimitiveNonNumeric() throws Exception - { - // bit tricky with binary fps but... - float value = Float.POSITIVE_INFINITY; - FloatBean result = MAPPER.readValue("{\"v\":\""+value+"\"}", FloatBean.class); - assertEquals(value, result._v); - - // should work with arrays too.. - float[] array = MAPPER.readValue("[ \"Infinity\" ]", float[].class); - assertNotNull(array); - assertEquals(1, array.length); - assertEquals(Float.POSITIVE_INFINITY, array[0]); - } - /* /********************************************************** /* Scalar tests, other @@ -526,7 +381,7 @@ public void testFloatPrimitiveNonNumeric() throws Exception public void testBase64Variants() throws Exception { final byte[] INPUT = "abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890X".getBytes("UTF-8"); - + // default encoding is "MIME, no linefeeds", so: Assert.assertArrayEquals(INPUT, MAPPER.readValue( quote("YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY3ODkwYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY3ODkwWA=="), @@ -547,7 +402,7 @@ public void testBase64Variants() throws Exception Assert.assertArrayEquals(INPUT, (byte[]) reader.with(Base64Variants.PEM).readValue( quote("YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY3ODkwYWJjZGVmZ2hpamts\\nbW5vcHFyc3R1dnd4eXoxMjM0NTY3ODkwWA==" ))); - } + } /* /********************************************************** @@ -568,7 +423,7 @@ public void testSequenceOfInts() throws Exception sb.append(" "); sb.append(i); } - JsonParser jp = MAPPER.getFactory().createParser(sb.toString()); + JsonParser jp = MAPPER.createParser(sb.toString()); for (int i = 0; i < NR_OF_INTS; ++i) { Integer result = MAPPER.readValue(jp, Integer.class); assertEquals(Integer.valueOf(i), result); @@ -576,21 +431,15 @@ public void testSequenceOfInts() throws Exception jp.close(); } - /* /********************************************************** /* Empty String coercion, handling /********************************************************** */ - // by default, should return nulls, n'est pas? - public void testEmptyStringForWrappers() throws IOException + public void testEmptyStringForIntegerWrappers() throws IOException { - WrappersBean bean; - - bean = MAPPER.readValue("{\"booleanValue\":\"\"}", WrappersBean.class); - assertNull(bean.booleanValue); - bean = MAPPER.readValue("{\"byteValue\":\"\"}", WrappersBean.class); + WrappersBean bean = MAPPER.readValue("{\"byteValue\":\"\"}", WrappersBean.class); assertNull(bean.byteValue); // char/Character is different... not sure if this should work or not: @@ -603,18 +452,25 @@ public void testEmptyStringForWrappers() throws IOException assertNull(bean.intValue); bean = MAPPER.readValue("{\"longValue\":\"\"}", WrappersBean.class); assertNull(bean.longValue); - bean = MAPPER.readValue("{\"floatValue\":\"\"}", WrappersBean.class); + } + + public void testEmptyStringForFloatWrappers() throws IOException + { + WrappersBean bean = MAPPER.readValue("{\"floatValue\":\"\"}", WrappersBean.class); assertNull(bean.floatValue); bean = MAPPER.readValue("{\"doubleValue\":\"\"}", WrappersBean.class); assertNull(bean.doubleValue); } - public void testEmptyStringForPrimitives() throws IOException + public void testEmptyStringForBooleanPrimitive() throws IOException { - PrimitivesBean bean; - bean = MAPPER.readValue("{\"booleanValue\":\"\"}", PrimitivesBean.class); + PrimitivesBean bean = MAPPER.readValue("{\"booleanValue\":\"\"}", PrimitivesBean.class); assertFalse(bean.booleanValue); - bean = MAPPER.readValue("{\"byteValue\":\"\"}", PrimitivesBean.class); + } + + public void testEmptyStringForIntegerPrimitives() throws IOException + { + PrimitivesBean bean = MAPPER.readValue("{\"byteValue\":\"\"}", PrimitivesBean.class); assertEquals((byte) 0, bean.byteValue); bean = MAPPER.readValue("{\"charValue\":\"\"}", PrimitivesBean.class); assertEquals((char) 0, bean.charValue); @@ -624,7 +480,11 @@ public void testEmptyStringForPrimitives() throws IOException assertEquals(0, bean.intValue); bean = MAPPER.readValue("{\"longValue\":\"\"}", PrimitivesBean.class); assertEquals(0L, bean.longValue); - bean = MAPPER.readValue("{\"floatValue\":\"\"}", PrimitivesBean.class); + } + + public void testEmptyStringForFloatPrimitives() throws IOException + { + PrimitivesBean bean = MAPPER.readValue("{\"floatValue\":\"\"}", PrimitivesBean.class); assertEquals(0.0f, bean.floatValue); bean = MAPPER.readValue("{\"doubleValue\":\"\"}", PrimitivesBean.class); assertEquals(0.0, bean.doubleValue); @@ -662,61 +522,90 @@ public void testNullForPrimitives() throws IOException try { reader.readValue("{\"booleanValue\":null}"); fail("Expected failure for boolean + null"); - } catch (JsonMappingException e) { - verifyException(e, "cannot map `null` into type boolean"); + } catch (MismatchedInputException e) { + verifyException(e, "Cannot map `null` into type boolean"); + verifyPath(e, "booleanValue"); } // byte/char/short/int/long try { reader.readValue("{\"byteValue\":null}"); fail("Expected failure for byte + null"); - } catch (JsonMappingException e) { - verifyException(e, "cannot map `null` into type byte"); + } catch (MismatchedInputException e) { + verifyException(e, "Cannot map `null` into type byte"); + verifyPath(e, "byteValue"); } try { reader.readValue("{\"charValue\":null}"); fail("Expected failure for char + null"); - } catch (JsonMappingException e) { - verifyException(e, "cannot map `null` into type char"); + } catch (MismatchedInputException e) { + verifyException(e, "Cannot map `null` into type char"); + verifyPath(e, "charValue"); } try { reader.readValue("{\"shortValue\":null}"); fail("Expected failure for short + null"); - } catch (JsonMappingException e) { - verifyException(e, "cannot map `null` into type short"); + } catch (MismatchedInputException e) { + verifyException(e, "Cannot map `null` into type short"); + verifyPath(e, "shortValue"); } try { reader.readValue("{\"intValue\":null}"); fail("Expected failure for int + null"); - } catch (JsonMappingException e) { - verifyException(e, "cannot map `null` into type int"); + } catch (MismatchedInputException e) { + verifyException(e, "Cannot coerce `null` to `int` value"); +// verifyPath(e, "intValue"); } try { reader.readValue("{\"longValue\":null}"); fail("Expected failure for long + null"); - } catch (JsonMappingException e) { - verifyException(e, "cannot map `null` into type long"); + } catch (MismatchedInputException e) { + verifyException(e, "Cannot coerce `null` to `long` value"); +// verifyPath(e, "longValue"); } // float/double try { reader.readValue("{\"floatValue\":null}"); fail("Expected failure for float + null"); - } catch (JsonMappingException e) { - verifyException(e, "cannot map `null` into type float"); + } catch (MismatchedInputException e) { + verifyException(e, "Cannot map `null` into type float"); + verifyPath(e, "floatValue"); } try { reader.readValue("{\"doubleValue\":null}"); fail("Expected failure for double + null"); - } catch (JsonMappingException e) { - verifyException(e, "cannot map `null` into type double"); + } catch (MismatchedInputException e) { + verifyException(e, "Cannot map `null` into type double"); + verifyPath(e, "doubleValue"); } } + // [databind#2101] + public void testNullForPrimitivesViaCreator() throws IOException + { + try { + /*PrimitiveCreatorBean bean =*/ MAPPER + .readerFor(PrimitiveCreatorBean.class) + .with(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES) + .readValue(aposToQuotes("{'a': null}")); + fail("Expected failure for `int` and `null`"); + } catch (MismatchedInputException e) { + verifyException(e, "Cannot map `null` into type int"); + verifyPath(e, "a"); + } + } + + private void verifyPath(MismatchedInputException e, String propName) { + final List path = e.getPath(); + assertEquals(1, path.size()); + assertEquals(propName, path.get(0).getFieldName()); + } + public void testNullForPrimitiveArrays() throws IOException { _testNullForPrimitiveArrays(boolean[].class, Boolean.FALSE); _testNullForPrimitiveArrays(byte[].class, Byte.valueOf((byte) 0)); - _testNullForPrimitiveArrays(char[].class, Character.valueOf((char) 0)); + _testNullForPrimitiveArrays(char[].class, Character.valueOf((char) 0), false); _testNullForPrimitiveArrays(short[].class, Short.valueOf((short)0)); _testNullForPrimitiveArrays(int[].class, Integer.valueOf(0)); _testNullForPrimitiveArrays(long[].class, Long.valueOf(0L)); @@ -724,24 +613,56 @@ public void testNullForPrimitiveArrays() throws IOException _testNullForPrimitiveArrays(double[].class, Double.valueOf(0d)); } - private void _testNullForPrimitiveArrays(Class cls, Object defValue) throws IOException + private void _testNullForPrimitiveArrays(Class cls, Object defValue) throws IOException { + _testNullForPrimitiveArrays(cls, defValue, true); + } + + private void _testNullForPrimitiveArrays(Class cls, Object defValue, + boolean testEmptyString) throws IOException { + final String EMPTY_STRING_JSON = "[ \"\" ]"; final String JSON_WITH_NULL = "[ null ]"; final String SIMPLE_NAME = "`"+cls.getSimpleName()+"`"; final ObjectReader readerCoerceOk = MAPPER.readerFor(cls); - final ObjectReader readerNoCoerce = readerCoerceOk + final ObjectReader readerNoNulls = readerCoerceOk .with(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES); Object ob = readerCoerceOk.forType(cls).readValue(JSON_WITH_NULL); assertEquals(1, Array.getLength(ob)); assertEquals(defValue, Array.get(ob, 0)); try { - readerNoCoerce.readValue(JSON_WITH_NULL); + readerNoNulls.readValue(JSON_WITH_NULL); fail("Should not pass"); } catch (JsonMappingException e) { verifyException(e, "Cannot coerce `null`"); verifyException(e, "to element of "+SIMPLE_NAME); } + + if (testEmptyString) { + ob = readerCoerceOk.forType(cls).readValue(EMPTY_STRING_JSON); + assertEquals(1, Array.getLength(ob)); + assertEquals(defValue, Array.get(ob, 0)); + + // Note: coercion tests moved to under "com.fasterxml.jackson.databind.convert" + } + } + + // [databind#2197], [databind#2679] + public void testVoidDeser() throws Exception + { + // First, `Void` as bean property + VoidBean bean = MAPPER.readValue(aposToQuotes("{'value' : 123 }"), + VoidBean.class); + assertNull(bean.value); + + // Then `Void` and `void` (Void.TYPE) as root values + assertNull(MAPPER.readValue("{}", Void.class)); + assertNull(MAPPER.readValue("1234", Void.class)); + assertNull(MAPPER.readValue("[ 1, true ]", Void.class)); + + assertNull(MAPPER.readValue("{}", Void.TYPE)); + assertNull(MAPPER.readValue("1234", Void.TYPE)); + assertNull(MAPPER.readValue("[ 1, true ]", Void.TYPE)); } /* @@ -776,9 +697,38 @@ private void _testInvalidStringCoercionFail(Class cls, String targetTypeName) try { MAPPER.readerFor(cls).readValue(JSON); - fail("Should not pass"); + fail("Should MismatchedInputException pass"); } catch (JsonMappingException e) { verifyException(e, "Cannot deserialize value of type `"+targetTypeName+"` from String \"foobar\""); } } + + /* + /********************************************************** + /* Tests for mismatch: JSON Object for scalars (not supported + /* for JSON + /********************************************************** + */ + + public void testFailForScalarFromObject() throws Exception + { + _testFailForNumberFromObject(Byte.TYPE); + _testFailForNumberFromObject(Short.TYPE); + _testFailForNumberFromObject(Long.TYPE); + _testFailForNumberFromObject(Float.TYPE); + _testFailForNumberFromObject(Double.TYPE); + _testFailForNumberFromObject(BigInteger.class); + _testFailForNumberFromObject(BigDecimal.class); + } + + private void _testFailForNumberFromObject(Class targetType) throws Exception + { + try { + MAPPER.readValue(a2q("{'value':12}"), targetType); + fail("Should not pass"); + } catch (MismatchedInputException e) { + verifyException(e, "from Object value"); + verifyException(e, ClassUtil.getClassDescription(targetType)); + } + } } diff --git a/blackbird/src/main/java/com/fasterxml/jackson/module/blackbird/ser/OptimizedBeanPropertyWriter.java b/blackbird/src/main/java/com/fasterxml/jackson/module/blackbird/ser/OptimizedBeanPropertyWriter.java index 671ef911..7e34496f 100644 --- a/blackbird/src/main/java/com/fasterxml/jackson/module/blackbird/ser/OptimizedBeanPropertyWriter.java +++ b/blackbird/src/main/java/com/fasterxml/jackson/module/blackbird/ser/OptimizedBeanPropertyWriter.java @@ -9,7 +9,6 @@ import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.jsontype.TypeSerializer; import com.fasterxml.jackson.databind.ser.BeanPropertyWriter; -import com.fasterxml.jackson.databind.util.ClassUtil; /** * Intermediate base class that is used for concrete diff --git a/blackbird/src/test/java/com/fasterxml/jackson/module/blackbird/deser/TestFailOnPrimitiveFromNullDeserialization.java b/blackbird/src/test/java/com/fasterxml/jackson/module/blackbird/deser/convert/TestFailOnPrimitiveFromNullDeserialization.java similarity index 97% rename from blackbird/src/test/java/com/fasterxml/jackson/module/blackbird/deser/TestFailOnPrimitiveFromNullDeserialization.java rename to blackbird/src/test/java/com/fasterxml/jackson/module/blackbird/deser/convert/TestFailOnPrimitiveFromNullDeserialization.java index 8f921326..b2123652 100644 --- a/blackbird/src/test/java/com/fasterxml/jackson/module/blackbird/deser/TestFailOnPrimitiveFromNullDeserialization.java +++ b/blackbird/src/test/java/com/fasterxml/jackson/module/blackbird/deser/convert/TestFailOnPrimitiveFromNullDeserialization.java @@ -1,4 +1,4 @@ -package com.fasterxml.jackson.module.blackbird.deser; +package com.fasterxml.jackson.module.blackbird.deser.convert; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper;