package com.fasterxml.jackson.databind.deser.std; import java.io.IOException; import java.util.*; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.Nulls; import com.fasterxml.jackson.core.*; import com.fasterxml.jackson.core.io.NumberInput; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.annotation.JacksonStdImpl; import com.fasterxml.jackson.databind.deser.BeanDeserializerBase; import com.fasterxml.jackson.databind.deser.NullValueProvider; import com.fasterxml.jackson.databind.deser.SettableBeanProperty; import com.fasterxml.jackson.databind.deser.ValueInstantiator; import com.fasterxml.jackson.databind.deser.impl.NullsAsEmptyProvider; import com.fasterxml.jackson.databind.deser.impl.NullsConstantProvider; import com.fasterxml.jackson.databind.deser.impl.NullsFailProvider; import com.fasterxml.jackson.databind.introspect.AnnotatedMember; import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; import com.fasterxml.jackson.databind.util.AccessPattern; import com.fasterxml.jackson.databind.util.ClassUtil; import com.fasterxml.jackson.databind.util.Converter; /** * Base class for common deserializers. Contains shared * base functionality for dealing with primitive values, such * as (re)parsing from String. */ public abstract class StdDeserializer<T> extends JsonDeserializer<T> implements java.io.Serializable { private static final long serialVersionUID = 1L; /** * Bitmask that covers {@link DeserializationFeature#USE_BIG_INTEGER_FOR_INTS} * and {@link DeserializationFeature#USE_LONG_FOR_INTS}, used for more efficient * cheks when coercing integral values for untyped deserialization. * * @since 2.6 */ protected final static int F_MASK_INT_COERCIONS = DeserializationFeature.USE_BIG_INTEGER_FOR_INTS.getMask() | DeserializationFeature.USE_LONG_FOR_INTS.getMask(); // @since 2.9 protected final static int F_MASK_ACCEPT_ARRAYS = DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS.getMask() | DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT.getMask(); /** * Type of values this deserializer handles: sometimes * exact types, other time most specific supertype of * types deserializer handles (which may be as generic * as {@link Object} in some case) */ final protected Class<?> _valueClass; protected StdDeserializer(Class<?> vc) { _valueClass = vc; } protected StdDeserializer(JavaType valueType) { _valueClass = valueType.getRawClass(); } /** * Copy-constructor for sub-classes to use, most often when creating * new instances for {@link com.fasterxml.jackson.databind.deser.ContextualDeserializer}. * * @since 2.5 */ protected StdDeserializer(StdDeserializer<?> src) { _valueClass = src._valueClass; } /* /********************************************************** /* Accessors /********************************************************** */ @Override public Class<?> handledType() { return _valueClass; } /* /********************************************************** /* Extended API /********************************************************** */ /** * @deprecated Since 2.3 use {@link #handledType} instead */ @Deprecated public final Class<?> getValueClass() { return _valueClass; } /** * Exact structured type this deserializer handles, if known. *<p> * Default implementation just returns null. */ public JavaType getValueType() { return null; } /** * Method that can be called to determine if given deserializer is the default * deserializer Jackson uses; as opposed to a custom deserializer installed by * a module or calling application. Determination is done using * {@link JacksonStdImpl} annotation on deserializer class. */ protected boolean isDefaultDeserializer(JsonDeserializer<?> deserializer) { return ClassUtil.isJacksonStdImpl(deserializer); } protected boolean isDefaultKeyDeserializer(KeyDeserializer keyDeser) { return ClassUtil.isJacksonStdImpl(keyDeser); } /* /********************************************************** /* Partial JsonDeserializer implementation /********************************************************** */ /** * Base implementation that does not assume specific type * inclusion mechanism. Sub-classes are expected to override * this method if they are to handle type information. */ @Override public Object deserializeWithType(JsonParser p, DeserializationContext ctxt, TypeDeserializer typeDeserializer) throws IOException { return typeDeserializer.deserializeTypedFromAny(p, ctxt); } /* /********************************************************** /* Helper methods for sub-classes, parsing: while mostly /* useful for numeric types, can be also useful for dealing /* with things serialized as numbers (such as Dates). /********************************************************** */ protected final boolean _parseBooleanPrimitive(JsonParser p, DeserializationContext ctxt) throws IOException { JsonToken t = p.getCurrentToken(); if (t == JsonToken.VALUE_TRUE) return true; if (t == JsonToken.VALUE_FALSE) return false; if (t == JsonToken.VALUE_NULL) { _verifyNullForPrimitive(ctxt); return false; } // should accept ints too, (0 == false, otherwise true) if (t == JsonToken.VALUE_NUMBER_INT) { return _parseBooleanFromInt(p, ctxt); } // And finally, let's allow Strings to be converted too if (t == JsonToken.VALUE_STRING) { String text = p.getText().trim(); // [databind#422]: Allow aliases if ("true".equals(text) || "True".equals(text)) { return true; } if ("false".equals(text) || "False".equals(text)) { return false; } if (_isEmptyOrTextualNull(text)) { _verifyNullForPrimitiveCoercion(ctxt, text); return false; } Boolean b = (Boolean) ctxt.handleWeirdStringValue(_valueClass, text, "only \"true\" or \"false\" recognized"); return Boolean.TRUE.equals(b); } // [databind#381] if (t == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { p.nextToken(); final boolean parsed = _parseBooleanPrimitive(p, ctxt); _verifyEndArrayForSingle(p, ctxt); return parsed; } // Otherwise, no can do: return ((Boolean) ctxt.handleUnexpectedToken(_valueClass, p)).booleanValue(); } protected boolean _parseBooleanFromInt(JsonParser p, DeserializationContext ctxt) throws IOException { // 13-Oct-2016, tatu: As per [databind#1324], need to be careful wrt // degenerate case of huge integers, legal in JSON. // ... this is, on the other hand, probably wrong/sub-optimal for non-JSON // input. For now, no rea _verifyNumberForScalarCoercion(ctxt, p); // Anyway, note that since we know it's valid (JSON) integer, it can't have // extra whitespace to trim. return !"0".equals(p.getText()); } protected final byte _parseBytePrimitive(JsonParser p, DeserializationContext ctxt) throws IOException { int value = _parseIntPrimitive(p, ctxt); // So far so good: but does it fit? if (_byteOverflow(value)) { Number v = (Number) ctxt.handleWeirdStringValue(_valueClass, String.valueOf(value), "overflow, value can not be represented as 8-bit value"); return _nonNullNumber(v).byteValue(); } return (byte) value; } protected final short _parseShortPrimitive(JsonParser p, DeserializationContext ctxt) throws IOException { 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 can not be represented as 16-bit value"); return _nonNullNumber(v).shortValue(); } return (short) value; } protected final int _parseIntPrimitive(JsonParser p, DeserializationContext ctxt) throws IOException { if (p.hasToken(JsonToken.VALUE_NUMBER_INT)) { return p.getIntValue(); } switch (p.getCurrentTokenId()) { case JsonTokenId.ID_STRING: String text = p.getText().trim(); if (_isEmptyOrTextualNull(text)) { _verifyNullForPrimitiveCoercion(ctxt, text); return 0; } return _parseIntPrimitive(ctxt, text); case JsonTokenId.ID_NUMBER_FLOAT: if (!ctxt.isEnabled(DeserializationFeature.ACCEPT_FLOAT_AS_INT)) { _failDoubleToIntCoercion(p, ctxt, "int"); } return p.getValueAsInt(); case JsonTokenId.ID_NULL: _verifyNullForPrimitive(ctxt); return 0; case JsonTokenId.ID_START_ARRAY: if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { p.nextToken(); final int parsed = _parseIntPrimitive(p, ctxt); _verifyEndArrayForSingle(p, ctxt); return parsed; } break; default: } // Otherwise, no can do: return ((Number) ctxt.handleUnexpectedToken(_valueClass, p)).intValue(); } /** * @since 2.9 */ protected 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(_valueClass, 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(_valueClass, text, "not a valid int value"); return _nonNullNumber(v).intValue(); } } protected final long _parseLongPrimitive(JsonParser p, DeserializationContext ctxt) throws IOException { if (p.hasToken(JsonToken.VALUE_NUMBER_INT)) { return p.getLongValue(); } switch (p.getCurrentTokenId()) { case JsonTokenId.ID_STRING: String text = p.getText().trim(); if (_isEmptyOrTextualNull(text)) { _verifyNullForPrimitiveCoercion(ctxt, text); return 0L; } return _parseLongPrimitive(ctxt, text); case JsonTokenId.ID_NUMBER_FLOAT: if (!ctxt.isEnabled(DeserializationFeature.ACCEPT_FLOAT_AS_INT)) { _failDoubleToIntCoercion(p, ctxt, "long"); } return p.getValueAsLong(); case JsonTokenId.ID_NULL: _verifyNullForPrimitive(ctxt); return 0L; case JsonTokenId.ID_START_ARRAY: if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { p.nextToken(); final long parsed = _parseLongPrimitive(p, ctxt); _verifyEndArrayForSingle(p, ctxt); return parsed; } break; } return ((Number) ctxt.handleUnexpectedToken(_valueClass, p)).longValue(); } /** * @since 2.9 */ protected final long _parseLongPrimitive(DeserializationContext ctxt, String text) throws IOException { try { return NumberInput.parseLong(text); } catch (IllegalArgumentException iae) { } { Number v = (Number) ctxt.handleWeirdStringValue(_valueClass, text, "not a valid long value"); return _nonNullNumber(v).longValue(); } } protected final float _parseFloatPrimitive(JsonParser p, DeserializationContext ctxt) throws IOException { if (p.hasToken(JsonToken.VALUE_NUMBER_FLOAT)) { return p.getFloatValue(); } switch (p.getCurrentTokenId()) { case JsonTokenId.ID_STRING: String text = p.getText().trim(); if (_isEmptyOrTextualNull(text)) { _verifyNullForPrimitiveCoercion(ctxt, text); return 0.0f; } return _parseFloatPrimitive(ctxt, text); case JsonTokenId.ID_NUMBER_INT: return p.getFloatValue(); case JsonTokenId.ID_NULL: _verifyNullForPrimitive(ctxt); return 0.0f; case JsonTokenId.ID_START_ARRAY: if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { p.nextToken(); final float parsed = _parseFloatPrimitive(p, ctxt); _verifyEndArrayForSingle(p, ctxt); return parsed; } break; } // Otherwise, no can do: return ((Number) ctxt.handleUnexpectedToken(_valueClass, p)).floatValue(); } /** * @since 2.9 */ protected final float _parseFloatPrimitive(DeserializationContext ctxt, String text) throws IOException { switch (text.charAt(0)) { case 'I': if (_isPosInf(text)) { return Float.POSITIVE_INFINITY; } break; case 'N': if (_isNaN(text)) { return Float.NaN; } break; case '-': if (_isNegInf(text)) { return Float.NEGATIVE_INFINITY; } break; } try { return Float.parseFloat(text); } catch (IllegalArgumentException iae) { } Number v = (Number) ctxt.handleWeirdStringValue(_valueClass, text, "not a valid float value"); return _nonNullNumber(v).floatValue(); } protected final double _parseDoublePrimitive(JsonParser p, DeserializationContext ctxt) throws IOException { if (p.hasToken(JsonToken.VALUE_NUMBER_FLOAT)) { return p.getDoubleValue(); } switch (p.getCurrentTokenId()) { case JsonTokenId.ID_STRING: String text = p.getText().trim(); if (_isEmptyOrTextualNull(text)) { _verifyNullForPrimitiveCoercion(ctxt, text); return 0.0; } return _parseDoublePrimitive(ctxt, text); case JsonTokenId.ID_NUMBER_INT: return p.getDoubleValue(); case JsonTokenId.ID_NULL: _verifyNullForPrimitive(ctxt); return 0.0; case JsonTokenId.ID_START_ARRAY: if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { p.nextToken(); final double parsed = _parseDoublePrimitive(p, ctxt); _verifyEndArrayForSingle(p, ctxt); return parsed; } break; } // Otherwise, no can do: return ((Number) ctxt.handleUnexpectedToken(_valueClass, p)).doubleValue(); } /** * @since 2.9 */ protected final double _parseDoublePrimitive(DeserializationContext ctxt, String text) throws IOException { switch (text.charAt(0)) { case 'I': if (_isPosInf(text)) { return Double.POSITIVE_INFINITY; } break; case 'N': if (_isNaN(text)) { return Double.NaN; } break; case '-': if (_isNegInf(text)) { return Double.NEGATIVE_INFINITY; } break; } try { return parseDouble(text); } catch (IllegalArgumentException iae) { } Number v = (Number) ctxt.handleWeirdStringValue(_valueClass, text, "not a valid double value"); return _nonNullNumber(v).doubleValue(); } protected java.util.Date _parseDate(JsonParser p, DeserializationContext ctxt) throws IOException { switch (p.getCurrentTokenId()) { case JsonTokenId.ID_STRING: return _parseDate(p.getText().trim(), ctxt); case JsonTokenId.ID_NUMBER_INT: return new java.util.Date(p.getLongValue()); case JsonTokenId.ID_NULL: return (java.util.Date) getNullValue(ctxt); case JsonTokenId.ID_START_ARRAY: return _parseDateFromArray(p, ctxt); } return (java.util.Date) ctxt.handleUnexpectedToken(_valueClass, p); } // @since 2.9 protected java.util.Date _parseDateFromArray(JsonParser p, DeserializationContext ctxt) throws IOException { JsonToken t; if (ctxt.hasSomeOfFeatures(F_MASK_ACCEPT_ARRAYS)) { t = p.nextToken(); if (t == JsonToken.END_ARRAY) { if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT)) { return (java.util.Date) getNullValue(ctxt); } } if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { final Date parsed = _parseDate(p, ctxt); _verifyEndArrayForSingle(p, ctxt); return parsed; } } else { t = p.getCurrentToken(); } return (java.util.Date) ctxt.handleUnexpectedToken(_valueClass, t, p, null); } /** * @since 2.8 */ protected java.util.Date _parseDate(String value, DeserializationContext ctxt) throws IOException { try { // Take empty Strings to mean 'empty' Value, usually 'null': if (_isEmptyOrTextualNull(value)) { return (java.util.Date) getNullValue(ctxt); } return ctxt.parseDate(value); } catch (IllegalArgumentException iae) { return (java.util.Date) ctxt.handleWeirdStringValue(_valueClass, value, "not a valid representation (error: %s)", iae.getMessage()); } } /** * Helper method for encapsulating calls to low-level double value parsing; single place * just because we need a work-around that must be applied to all calls. */ protected final static double parseDouble(String numStr) throws NumberFormatException { // avoid some nasty float representations... but should it be MIN_NORMAL or MIN_VALUE? if (NumberInput.NASTY_SMALL_DOUBLE.equals(numStr)) { return Double.MIN_NORMAL; // since 2.7; was MIN_VALUE prior } return Double.parseDouble(numStr); } /** * Helper method used for accessing String value, if possible, doing * necessary conversion or throwing exception as necessary. * * @since 2.1 */ protected final String _parseString(JsonParser p, DeserializationContext ctxt) throws IOException { JsonToken t = p.getCurrentToken(); if (t == JsonToken.VALUE_STRING) { return p.getText(); } // 07-Nov-2016, tatu: Caller should take care of unwrapping and there shouldn't // be need for extra pass here... /* // [databind#381] if ((t == JsonToken.START_ARRAY) && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { p.nextToken(); final String parsed = _parseString(p, ctxt); _verifyEndArrayForSingle(p, ctxt); return parsed; } */ String value = p.getValueAsString(); if (value != null) { return value; } return (String) ctxt.handleUnexpectedToken(String.class, p); } /** * Helper method that may be used to support fallback for Empty String / Empty Array * non-standard representations; usually for things serialized as JSON Objects. * * @since 2.5 */ @SuppressWarnings("unchecked") protected T _deserializeFromEmpty(JsonParser p, DeserializationContext ctxt) throws IOException { JsonToken t = p.getCurrentToken(); if (t == JsonToken.START_ARRAY) { if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT)) { t = p.nextToken(); if (t == JsonToken.END_ARRAY) { return null; } return (T) ctxt.handleUnexpectedToken(handledType(), p); } } else if (t == JsonToken.VALUE_STRING) { if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)) { String str = p.getText().trim(); if (str.isEmpty()) { return null; } } } return (T) ctxt.handleUnexpectedToken(handledType(), p); } /** * Helper method called to determine if we are seeing String value of * "null", and, further, that it should be coerced to null just like * null token. * * @since 2.3 */ protected boolean _hasTextualNull(String value) { return "null".equals(value); } /** * @since 2.9 */ protected boolean _isEmptyOrTextualNull(String value) { return value.isEmpty() || "null".equals(value); } protected final boolean _isNegInf(String text) { return "-Infinity".equals(text) || "-INF".equals(text); } protected final boolean _isPosInf(String text) { return "Infinity".equals(text) || "INF".equals(text); } protected final boolean _isNaN(String text) { return "NaN".equals(text); } /* /********************************************************** /* Helper methods for sub-classes regarding decoding from /* alternate representations /********************************************************** */ /** * Helper method that allows easy support for array-related {@link DeserializationFeature}s * `ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT` and `UNWRAP_SINGLE_VALUE_ARRAYS`: checks for either * empty array, or single-value array-wrapped value (respectively), and either reports * an exception (if no match, or feature(s) not enabled), or returns appropriate * result value. *<p> * This method should NOT be called if Array representation is explicitly supported * for type: it should only be called in case it is otherwise unrecognized. *<p> * NOTE: in case of unwrapped single element, will handle actual decoding * by calling {@link #_deserializeWrappedValue}, which by default calls * {@link #deserialize(JsonParser, DeserializationContext)}. * * @since 2.9 */ protected T _deserializeFromArray(JsonParser p, DeserializationContext ctxt) throws IOException { JsonToken t; if (ctxt.hasSomeOfFeatures(F_MASK_ACCEPT_ARRAYS)) { t = p.nextToken(); if (t == JsonToken.END_ARRAY) { if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT)) { return getNullValue(ctxt); } } if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { final T parsed = deserialize(p, ctxt); if (p.nextToken() != JsonToken.END_ARRAY) { handleMissingEndArrayForSingle(p, ctxt); } return parsed; } } else { t = p.getCurrentToken(); } @SuppressWarnings("unchecked") T result = (T) ctxt.handleUnexpectedToken(_valueClass, t, p, null); return result; } /** * Helper called to support {@link DeserializationFeature#UNWRAP_SINGLE_VALUE_ARRAYS}: * default implementation simply calls * {@link #deserialize(JsonParser, DeserializationContext)}, * but handling may be overridden. * * @since 2.9 */ protected T _deserializeWrappedValue(JsonParser p, DeserializationContext ctxt) throws IOException { // 23-Mar-2017, tatu: Let's specifically block recursive resolution to avoid // either supporting nested arrays, or to cause infinite looping. if (p.hasToken(JsonToken.START_ARRAY)) { String msg = String.format( "Can not deserialize instance of %s out of %s token: nested Arrays not allowed with %s", ClassUtil.nameOf(_valueClass), JsonToken.START_ARRAY, "DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS"); @SuppressWarnings("unchecked") T result = (T) ctxt.handleUnexpectedToken(_valueClass, p.getCurrentToken(), p, msg); return result; } return (T) deserialize(p, ctxt); } /* /**************************************************** /* Helper methods for sub-classes, coercions /**************************************************** */ protected void _failDoubleToIntCoercion(JsonParser p, DeserializationContext ctxt, String type) throws IOException { ctxt.reportInputMismatch(handledType(), "Can not coerce a floating-point value ('%s') into %s (enable `DeserializationFeature.ACCEPT_FLOAT_AS_INT` to allow)", p.getValueAsString(), type); } /** * Helper method called in case where an integral number is encountered, but * config settings suggest that a coercion may be needed to "upgrade" * {@link java.lang.Number} into "bigger" type like {@link java.lang.Long} or * {@link java.math.BigInteger} * * @see DeserializationFeature#USE_BIG_INTEGER_FOR_INTS * @see DeserializationFeature#USE_LONG_FOR_INTS * * @since 2.6 */ protected Object _coerceIntegral(JsonParser p, DeserializationContext ctxt) throws IOException { int feats = ctxt.getDeserializationFeatures(); if (DeserializationFeature.USE_BIG_INTEGER_FOR_INTS.enabledIn(feats)) { return p.getBigIntegerValue(); } if (DeserializationFeature.USE_LONG_FOR_INTS.enabledIn(feats)) { return p.getLongValue(); } return p.getBigIntegerValue(); // should be optimal, whatever it is } /** * Method to call when JSON `null` token is encountered. Note: only called when * this deserializer encounters it but NOT when reached via property * * @since 2.9 */ protected Object _coerceNullToken(DeserializationContext ctxt, boolean isPrimitive) throws JsonMappingException { if (isPrimitive) { _verifyNullForPrimitive(ctxt); } return getNullValue(ctxt); } /** * Method called when JSON String with value "null" is encountered. * * @since 2.9 */ protected Object _coerceTextualNull(DeserializationContext ctxt, boolean isPrimitive) throws JsonMappingException { Enum<?> feat; boolean enable; if (!ctxt.isEnabled(MapperFeature.ALLOW_COERCION_OF_SCALARS)) { feat = MapperFeature.ALLOW_COERCION_OF_SCALARS; enable = true; } else if (isPrimitive && ctxt.isEnabled(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)) { feat = DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES; enable = false; } else { return getNullValue(ctxt); } _reportFailedNullCoerce(ctxt, enable, feat, "String \"null\""); return null; } /** * Method called when JSON String with value "" (that is, zero length) is encountered. * * @since 2.9 */ protected Object _coerceEmptyString(DeserializationContext ctxt, boolean isPrimitive) throws JsonMappingException { Enum<?> feat; boolean enable; if (!ctxt.isEnabled(MapperFeature.ALLOW_COERCION_OF_SCALARS)) { feat = MapperFeature.ALLOW_COERCION_OF_SCALARS; enable = true; } else if (isPrimitive && ctxt.isEnabled(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)) { feat = DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES; enable = false; } else { return getNullValue(ctxt); } _reportFailedNullCoerce(ctxt, enable, feat, "empty String (\"\")"); return null; } // @since 2.9 protected final void _verifyNullForPrimitive(DeserializationContext ctxt) throws JsonMappingException { if (ctxt.isEnabled(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)) { ctxt.reportInputMismatch(this, "Can not coerce `null` %s (disable `DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES` to allow)", _coercedTypeDesc()); } } // NOTE: only for primitive Scalars // @since 2.9 protected final void _verifyNullForPrimitiveCoercion(DeserializationContext ctxt, String str) throws JsonMappingException { Enum<?> feat; boolean enable; if (!ctxt.isEnabled(MapperFeature.ALLOW_COERCION_OF_SCALARS)) { feat = MapperFeature.ALLOW_COERCION_OF_SCALARS; enable = true; } else if (ctxt.isEnabled(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)) { feat = DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES; enable = false; } else { return; } String strDesc = str.isEmpty() ? "empty String (\"\")" : String.format("String \"%s\"", str); _reportFailedNullCoerce(ctxt, enable, feat, strDesc); } // NOTE: for non-primitive Scalars // @since 2.9 protected final void _verifyNullForScalarCoercion(DeserializationContext ctxt, String str) throws JsonMappingException { if (!ctxt.isEnabled(MapperFeature.ALLOW_COERCION_OF_SCALARS)) { String strDesc = str.isEmpty() ? "empty String (\"\")" : String.format("String \"%s\"", str); _reportFailedNullCoerce(ctxt, true, MapperFeature.ALLOW_COERCION_OF_SCALARS, strDesc); } } // @since 2.9 protected void _verifyStringForScalarCoercion(DeserializationContext ctxt, String str) throws JsonMappingException { MapperFeature feat = MapperFeature.ALLOW_COERCION_OF_SCALARS; if (!ctxt.isEnabled(feat)) { ctxt.reportInputMismatch(this, "Can not coerce String \"%s\" %s (enable `%s.%s` to allow)", str, _coercedTypeDesc(), feat.getClass().getSimpleName(), feat.name()); } } // @since 2.9 protected void _verifyNumberForScalarCoercion(DeserializationContext ctxt, JsonParser p) throws IOException { MapperFeature feat = MapperFeature.ALLOW_COERCION_OF_SCALARS; if (!ctxt.isEnabled(feat)) { // 31-Mar-2017, tatu: Since we don't know (or this deep, care) about exact type, // access as a String: may require re-encoding by parser which should be fine String valueDesc = p.getText(); ctxt.reportInputMismatch(this, "Can not coerce Number (%s) %s (enable `%s.%s` to allow)", valueDesc, _coercedTypeDesc(), feat.getClass().getSimpleName(), feat.name()); } } protected void _reportFailedNullCoerce(DeserializationContext ctxt, boolean state, Enum<?> feature, String inputDesc) throws JsonMappingException { String enableDesc = state ? "enable" : "disable"; ctxt.reportInputMismatch(this, "Can not coerce %s to Null value %s (%s `%s.%s` to allow)", inputDesc, _coercedTypeDesc(), enableDesc, feature.getClass().getSimpleName(), feature.name()); } /** * Helper method called to get a description of type into which a scalar value coercion * is (most likely) being applied, to be used for constructing exception messages * on coerce failure. * * @since 2.9 */ protected String _coercedTypeDesc() { boolean structured; String typeDesc; JavaType t = getValueType(); if (t != null) { structured = (t.isContainerType() || t.isReferenceType()); typeDesc = t.toString(); } else { Class<?> cls = handledType(); structured = cls.isArray() || Collection.class.isAssignableFrom(cls) || Map.class.isAssignableFrom(cls); typeDesc = ClassUtil.nameOf(cls); } if (structured) { return String.format("as content of type `%s`", typeDesc); } return String.format("for type `%s`", typeDesc); } /* /**************************************************** /* Helper methods for sub-classes, resolving dependencies /**************************************************** */ /** * Helper method used to locate deserializers for properties the * type this deserializer handles contains (usually for properties of * bean types) * * @param type Type of property to deserialize * @param property Actual property object (field, method, constuctor parameter) used * for passing deserialized values; provided so deserializer can be contextualized if necessary */ protected JsonDeserializer<Object> findDeserializer(DeserializationContext ctxt, JavaType type, BeanProperty property) throws JsonMappingException { return ctxt.findContextualValueDeserializer(type, property); } /** * Helper method to check whether given text refers to what looks like a clean simple * integer number, consisting of optional sign followed by a sequence of digits. */ protected final boolean _isIntNumber(String text) { final int len = text.length(); if (len > 0) { char c = text.charAt(0); // skip leading sign (plus not allowed for strict JSON numbers but...) int i = (c == '-' || c == '+') ? 1 : 0; for (; i < len; ++i) { int ch = text.charAt(i); if (ch > '9' || ch < '0') { return false; } } return true; } return false; } /* /********************************************************** /* Helper methods for: deserializer construction /********************************************************** */ /** * Helper method that can be used to see if specified property has annotation * indicating that a converter is to be used for contained values (contents * of structured types; array/List/Map values) * * @param existingDeserializer (optional) configured content * serializer if one already exists. * * @since 2.2 */ protected JsonDeserializer<?> findConvertingContentDeserializer(DeserializationContext ctxt, BeanProperty prop, JsonDeserializer<?> existingDeserializer) throws JsonMappingException { final AnnotationIntrospector intr = ctxt.getAnnotationIntrospector(); if (_neitherNull(intr, prop)) { AnnotatedMember member = prop.getMember(); if (member != null) { Object convDef = intr.findDeserializationContentConverter(member); if (convDef != null) { Converter<Object,Object> conv = ctxt.converterInstance(prop.getMember(), convDef); JavaType delegateType = conv.getInputType(ctxt.getTypeFactory()); if (existingDeserializer == null) { existingDeserializer = ctxt.findContextualValueDeserializer(delegateType, prop); } return new StdDelegatingDeserializer<Object>(conv, delegateType, existingDeserializer); } } } return existingDeserializer; } /* /********************************************************** /* Helper methods for: accessing contextual config settings /********************************************************** */ /** * Helper method that may be used to find if this deserializer has specific * {@link JsonFormat} settings, either via property, or through type-specific * defaulting. * * @param typeForDefaults Type (erased) used for finding default format settings, if any * * @since 2.7 */ protected JsonFormat.Value findFormatOverrides(DeserializationContext ctxt, BeanProperty prop, Class<?> typeForDefaults) { if (prop != null) { return prop.findPropertyFormat(ctxt.getConfig(), typeForDefaults); } // even without property or AnnotationIntrospector, may have type-specific defaults return ctxt.getDefaultPropertyFormat(typeForDefaults); } /** * Convenience method that uses {@link #findFormatOverrides} to find possible * defaults and/of overrides, and then calls * <code>JsonFormat.Value.getFeature(feat)</code> * to find whether that feature has been specifically marked as enabled or disabled. * * @param typeForDefaults Type (erased) used for finding default format settings, if any * * @since 2.7 */ protected Boolean findFormatFeature(DeserializationContext ctxt, BeanProperty prop, Class<?> typeForDefaults, JsonFormat.Feature feat) { JsonFormat.Value format = findFormatOverrides(ctxt, prop, typeForDefaults); if (format != null) { return format.getFeature(feat); } return null; } /** * Method called to find {@link NullValueProvider} for a primary property, using * "value nulls" setting. If no provider found (not defined, or is "skip"), * will return `null`. * * @since 2.9 */ protected final NullValueProvider findValueNullProvider(DeserializationContext ctxt, SettableBeanProperty prop, PropertyMetadata propMetadata) throws JsonMappingException { if (prop != null) { return _findNullProvider(ctxt, prop, propMetadata.getValueNulls(), prop.getValueDeserializer()); } return null; } /** * Method called to find {@link NullValueProvider} for a contents of a structured * primary property (Collection, Map, array), using * "content nulls" setting. If no provider found (not defined), * will return given value deserializer (which is a null value provider itself). * * @since 2.9 */ protected NullValueProvider findContentNullProvider(DeserializationContext ctxt, BeanProperty prop, JsonDeserializer<?> valueDeser) throws JsonMappingException { final Nulls nulls = findContentNullStyle(ctxt, prop); if (nulls == Nulls.SKIP) { return NullsConstantProvider.skipper(); } NullValueProvider prov = _findNullProvider(ctxt, prop, nulls, valueDeser); if (prov != null) { return prov; } return valueDeser; } protected Nulls findContentNullStyle(DeserializationContext ctxt, BeanProperty prop) throws JsonMappingException { if (prop != null) { return prop.getMetadata().getContentNulls(); } return null; } // @since 2.9 protected final NullValueProvider _findNullProvider(DeserializationContext ctxt, BeanProperty prop, Nulls nulls, JsonDeserializer<?> valueDeser) throws JsonMappingException { if (nulls == Nulls.FAIL) { if (prop == null) { return NullsFailProvider.constructForRootValue(ctxt.constructType(valueDeser.handledType())); } return NullsFailProvider.constructForProperty(prop); } if (nulls == Nulls.AS_EMPTY) { // can not deal with empty values if there is no value deserializer that // can indicate what "empty value" is: if (valueDeser == null) { return null; } // Let's first do some sanity checking... // NOTE: although we could use `ValueInstantiator.Gettable` in general, // let's not since that would prevent being able to use custom impls: if (valueDeser instanceof BeanDeserializerBase) { ValueInstantiator vi = ((BeanDeserializerBase) valueDeser).getValueInstantiator(); if (!vi.canCreateUsingDefault()) { final JavaType type = prop.getType(); ctxt.reportBadDefinition(type, String.format("Can not create empty instance of %s, no default Creator", type)); } } // Second: can with pre-fetch value? { AccessPattern access = valueDeser.getEmptyAccessPattern(); if (access == AccessPattern.ALWAYS_NULL) { return NullsConstantProvider.nuller(); } if (access == AccessPattern.CONSTANT) { return NullsConstantProvider.forValue(valueDeser.getEmptyValue(ctxt)); } } return new NullsAsEmptyProvider(valueDeser); } if (nulls == Nulls.SKIP) { return NullsConstantProvider.skipper(); } return null; } /* /********************************************************** /* Helper methods for sub-classes, problem reporting /********************************************************** */ /** * Method called to deal with a property that did not map to a known * Bean property. Method can deal with the problem as it sees fit (ignore, * throw exception); but if it does return, it has to skip the matching * Json content parser has. * * @param p Parser that points to value of the unknown property * @param ctxt Context for deserialization; allows access to the parser, * error reporting functionality * @param instanceOrClass Instance that is being populated by this * deserializer, or if not known, Class that would be instantiated. * If null, will assume type is what {@link #getValueClass} returns. * @param propName Name of the property that can not be mapped */ protected void handleUnknownProperty(JsonParser p, DeserializationContext ctxt, Object instanceOrClass, String propName) throws IOException { if (instanceOrClass == null) { instanceOrClass = handledType(); } // Maybe we have configured handler(s) to take care of it? if (ctxt.handleUnknownProperty(p, this, instanceOrClass, propName)) { return; } /* But if we do get this far, need to skip whatever value we * are pointing to now (although handler is likely to have done that already) */ p.skipChildren(); } protected void handleMissingEndArrayForSingle(JsonParser p, DeserializationContext ctxt) throws IOException { ctxt.reportWrongTokenException(this, JsonToken.END_ARRAY, "Attempted to unwrap '%s' value from an array (with `DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS`) but it contains more than one value", handledType().getName()); // 05-May-2016, tatu: Should recover somehow (maybe skip until END_ARRAY); // but for now just fall through } protected void _verifyEndArrayForSingle(JsonParser p, DeserializationContext ctxt) throws IOException { JsonToken t = p.nextToken(); if (t != JsonToken.END_ARRAY) { handleMissingEndArrayForSingle(p, ctxt); } } /* /********************************************************** /* Helper methods, other /********************************************************** */ /** * @since 2.9 */ protected final static boolean _neitherNull(Object a, Object b) { return (a != null) && (b != null); } /** * @since 2.9 */ protected final boolean _byteOverflow(int value) { // 07-nov-2016, tatu: We support "unsigned byte" as well // as Java signed range since that's relatively common usage return (value < Byte.MIN_VALUE || value > 255); } /** * @since 2.9 */ protected final boolean _shortOverflow(int value) { return (value < Short.MIN_VALUE || value > Short.MAX_VALUE); } /** * @since 2.9 */ protected final boolean _intOverflow(long value) { return (value < Integer.MIN_VALUE || value > Integer.MAX_VALUE); } /** * @since 2.9 */ protected Number _nonNullNumber(Number n) { if (n == null) { n = Integer.valueOf(0); } return n; } }