com.fasterxml.jackson.databind.deser.std.StdDeserializer Maven / Gradle / Ivy
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.JsonParser.NumberType;
import com.fasterxml.jackson.core.exc.StreamReadException;
import com.fasterxml.jackson.core.io.NumberInput;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import com.fasterxml.jackson.databind.cfg.CoercionAction;
import com.fasterxml.jackson.databind.cfg.CoercionInputShape;
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.type.LogicalType;
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
extends JsonDeserializer
implements java.io.Serializable,
ValueInstantiator.Gettable // since 2.12
{
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();
@Deprecated // since 2.12
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;
final protected JavaType _valueType;
protected StdDeserializer(Class> vc) {
_valueClass = vc;
_valueType = null;
}
protected StdDeserializer(JavaType valueType) {
// 26-Sep-2017, tatu: [databind#1764] need to add null-check back until 3.x
_valueClass = (valueType == null) ? Object.class : valueType.getRawClass();
_valueType = valueType;
}
/**
* 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;
_valueType = src._valueType;
}
/*
/**********************************************************
/* 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.
*/
public JavaType getValueType() { return _valueType; }
/**
* Convenience method for getting handled type as {@link JavaType}, regardless
* of whether deserializer has one already resolved (and accessible via
* {@link #getValueType()}) or not: equivalent to:
*
* if (getValueType() != null) {
* return getValueType();
* }
* return ctxt.constructType(handledType());
*
*
* @since 2.10
*/
public JavaType getValueType(DeserializationContext ctxt) {
if (_valueType != null) {
return _valueType;
}
return ctxt.constructType(_valueClass);
}
/**
* @since 2.12
*/
@Override // for ValueInstantiator.Gettable
public ValueInstantiator getValueInstantiator() { 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);
}
/*
/**********************************************************************
/* High-level handling of secondary input shapes (with possible coercion)
/**********************************************************************
*/
/**
* Helper method that allows easy support for array-related coercion features:
* checks for either empty array, or single-value array-wrapped value (if coercion
* enabled by {@code CoercionConfigs} (since 2.12), and either reports
* an exception (if no coercion allowed), or returns appropriate
* result value using coercion mechanism indicated.
*
* 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.
*
* 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
*/
@SuppressWarnings("unchecked")
protected T _deserializeFromArray(JsonParser p, DeserializationContext ctxt) throws IOException
{
final CoercionAction act = _findCoercionFromEmptyArray(ctxt);
final boolean unwrap = ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
if (unwrap || (act != CoercionAction.Fail)) {
JsonToken t = p.nextToken();
if (t == JsonToken.END_ARRAY) {
switch (act) {
case AsEmpty:
return (T) getEmptyValue(ctxt);
case AsNull:
case TryConvert:
return getNullValue(ctxt);
default:
}
} else if (unwrap) {
final T parsed = _deserializeWrappedValue(p, ctxt);
if (p.nextToken() != JsonToken.END_ARRAY) {
handleMissingEndArrayForSingle(p, ctxt);
}
return parsed;
}
}
return (T) ctxt.handleUnexpectedToken(getValueType(ctxt), JsonToken.START_ARRAY, p, null);
}
/**
* 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
*
* @deprecated Since 2.12
*/
@SuppressWarnings("unchecked")
@Deprecated // since 2.12
protected T _deserializeFromEmpty(JsonParser p, DeserializationContext ctxt)
throws IOException
{
if (p.hasToken(JsonToken.START_ARRAY)) {
if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT)) {
JsonToken t = p.nextToken();
if (t == JsonToken.END_ARRAY) {
return null;
}
return (T) ctxt.handleUnexpectedToken(getValueType(ctxt), p);
}
}
return (T) ctxt.handleUnexpectedToken(getValueType(ctxt), p);
}
/**
* Helper method to call in case deserializer does not support native automatic
* use of incoming String values, but there may be standard coercions to consider.
*
* @since 2.12
*/
@SuppressWarnings("unchecked")
protected T _deserializeFromString(JsonParser p, DeserializationContext ctxt)
throws IOException
{
final ValueInstantiator inst = getValueInstantiator();
final Class> rawTargetType = handledType();
String value = p.getValueAsString();
if ((inst != null) && inst.canCreateFromString()) {
return (T) inst.createFromString(ctxt, value);
}
if (value.isEmpty()) {
final CoercionAction act = ctxt.findCoercionAction(logicalType(), rawTargetType,
CoercionInputShape.EmptyString);
return (T) _deserializeFromEmptyString(p, ctxt, act, rawTargetType,
"empty String (\"\")");
}
if (_isBlank(value)) {
final CoercionAction act = ctxt.findCoercionFromBlankString(logicalType(), rawTargetType,
CoercionAction.Fail);
return (T) _deserializeFromEmptyString(p, ctxt, act, rawTargetType,
"blank String (all whitespace)");
}
// 28-Sep-2011, tatu: Ok this is not clean at all; but since there are legacy
// systems that expect conversions in some cases, let's just add a minimal
// patch (note: same could conceivably be used for numbers too).
if (inst != null) {
value = value.trim(); // mostly to avoid problems wrt XML indentation
if (inst.canCreateFromInt()) {
if (ctxt.findCoercionAction(LogicalType.Integer, Integer.class,
CoercionInputShape.String) == CoercionAction.TryConvert) {
return (T) inst.createFromInt(ctxt, _parseIntPrimitive(ctxt, value));
}
}
if (inst.canCreateFromLong()) {
if (ctxt.findCoercionAction(LogicalType.Integer, Long.class,
CoercionInputShape.String) == CoercionAction.TryConvert) {
return (T) inst.createFromLong(ctxt, _parseLongPrimitive(ctxt, value));
}
}
if (inst.canCreateFromBoolean()) {
// 29-May-2020, tatu: With 2.12 can and should use CoercionConfig so:
if (ctxt.findCoercionAction(LogicalType.Boolean, Boolean.class,
CoercionInputShape.String) == CoercionAction.TryConvert) {
String str = value.trim();
if ("true".equals(str)) {
return (T) inst.createFromBoolean(ctxt, true);
}
if ("false".equals(str)) {
return (T) inst.createFromBoolean(ctxt, false);
}
}
}
}
return (T) ctxt.handleMissingInstantiator(rawTargetType, inst, ctxt.getParser(),
"no String-argument constructor/factory method to deserialize from String value ('%s')",
value);
}
protected Object _deserializeFromEmptyString(JsonParser p, DeserializationContext ctxt,
CoercionAction act, Class> rawTargetType, String desc) throws IOException
{
switch (act) {
case AsEmpty:
return getEmptyValue(ctxt);
case Fail:
// This will throw an exception
_checkCoercionFail(ctxt, act, rawTargetType, "", "empty String (\"\")");
// ... so will never fall through
case TryConvert:
// hmmmh... empty or null, typically? Assume "as null" for now
case AsNull:
default:
return null;
}
// 06-Nov-2020, tatu: This was behavior pre-2.12, giving less useful
// exception
/*
final ValueInstantiator inst = getValueInstantiator();
// 03-Jun-2020, tatu: Should ideally call `handleUnexpectedToken()` instead, but
// since this call was already made, use it.
return ctxt.handleMissingInstantiator(rawTargetType, inst, p,
"Cannot deserialize value of type %s from %s (no String-argument constructor/factory method; coercion not enabled)",
ClassUtil.getTypeDescription(getValueType(ctxt)), desc);
*/
}
/**
* 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)) {
@SuppressWarnings("unchecked")
T result = (T) handleNestedArrayForSingle(p, ctxt);
return result;
}
return (T) deserialize(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).
/**********************************************************
*/
@Deprecated // since 2.12, use overloaded variant that does NOT take target type
protected final boolean _parseBooleanPrimitive(DeserializationContext ctxt,
JsonParser p, Class> targetType) throws IOException {
return _parseBooleanPrimitive(p, ctxt);
}
/**
* @param ctxt Deserialization context for accessing configuration
* @param p Underlying parser
*/
protected final boolean _parseBooleanPrimitive(JsonParser p, DeserializationContext ctxt)
throws IOException
{
String text;
switch (p.currentTokenId()) {
case JsonTokenId.ID_STRING:
text = p.getText();
break;
case JsonTokenId.ID_NUMBER_INT:
// may accept ints too, (0 == false, otherwise true)
// call returns `null`, Boolean.TRUE or Boolean.FALSE so:
return Boolean.TRUE.equals(_coerceBooleanFromInt(p, ctxt, Boolean.TYPE));
case JsonTokenId.ID_TRUE: // usually caller should have handled but:
return true;
case JsonTokenId.ID_FALSE:
return false;
case JsonTokenId.ID_NULL: // null fine for non-primitive
_verifyNullForPrimitive(ctxt);
return false;
// 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
case JsonTokenId.ID_START_OBJECT:
text = ctxt.extractScalarFromObject(p, this, Boolean.TYPE);
break;
case JsonTokenId.ID_START_ARRAY:
// 12-Jun-2020, tatu: For some reason calling `_deserializeFromArray()` won't work so:
if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
if (p.nextToken() == JsonToken.START_ARRAY) {
return (boolean) handleNestedArrayForSingle(p, ctxt);
}
final boolean parsed = _parseBooleanPrimitive(p, ctxt);
_verifyEndArrayForSingle(p, ctxt);
return parsed;
}
// fall through
default:
return ((Boolean) ctxt.handleUnexpectedToken(Boolean.TYPE, p)).booleanValue();
}
final CoercionAction act = _checkFromStringCoercion(ctxt, text,
LogicalType.Boolean, Boolean.TYPE);
if (act == CoercionAction.AsNull) {
_verifyNullForPrimitive(ctxt);
return false;
}
if (act == CoercionAction.AsEmpty) {
return false;
}
text = text.trim();
final int len = text.length();
// For [databind#1852] allow some case-insensitive matches (namely,
// true/True/TRUE, false/False/FALSE
if (len == 4) {
if (_isTrue(text)) {
return true;
}
} else if (len == 5) {
if (_isFalse(text)) {
return false;
}
}
if (_hasTextualNull(text)) {
_verifyNullForPrimitiveCoercion(ctxt, text);
return false;
}
Boolean b = (Boolean) ctxt.handleWeirdStringValue(Boolean.TYPE, text,
"only \"true\"/\"True\"/\"TRUE\" or \"false\"/\"False\"/\"FALSE\" recognized");
return Boolean.TRUE.equals(b);
}
// [databind#1852]
protected boolean _isTrue(String text) {
char c = text.charAt(0);
if (c == 't') {
return "true".equals(text);
}
if (c == 'T') {
return "TRUE".equals(text) || "True".equals(text);
}
return false;
}
protected boolean _isFalse(String text) {
char c = text.charAt(0);
if (c == 'f') {
return "false".equals(text);
}
if (c == 'F') {
return "FALSE".equals(text) || "False".equals(text);
}
return false;
}
/**
* Helper method called for cases where non-primitive, boolean-based value
* is to be deserialized: result of this method will be {@link java.lang.Boolean},
* although actual target type may be something different.
*
* Note: does NOT dynamically access "empty value" or "null value" of deserializer
* since those values could be of type other than {@link java.lang.Boolean}.
* Caller may need to translate from 3 possible result types into appropriately
* matching output types.
*
* @param p Underlying parser
* @param ctxt Deserialization context for accessing configuration
* @param targetType Actual type that is being deserialized, may be
* same as {@link #handledType} but could be {@code AtomicBoolean} for example.
* Used for coercion config access.
*
* @since 2.12
*/
protected final Boolean _parseBoolean(JsonParser p, DeserializationContext ctxt,
Class> targetType)
throws IOException
{
String text;
switch (p.currentTokenId()) {
case JsonTokenId.ID_STRING:
text = p.getText();
break;
case JsonTokenId.ID_NUMBER_INT:
// may accept ints too, (0 == false, otherwise true)
return _coerceBooleanFromInt(p, ctxt, targetType);
case JsonTokenId.ID_TRUE:
return true;
case JsonTokenId.ID_FALSE:
return false;
case JsonTokenId.ID_NULL: // null fine for non-primitive
return null;
// 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
case JsonTokenId.ID_START_OBJECT:
text = ctxt.extractScalarFromObject(p, this, targetType);
break;
case JsonTokenId.ID_START_ARRAY: // unwrapping / from-empty-array coercion?
return (Boolean) _deserializeFromArray(p, ctxt);
default:
return (Boolean) ctxt.handleUnexpectedToken(targetType, p);
}
final CoercionAction act = _checkFromStringCoercion(ctxt, text,
LogicalType.Boolean, targetType);
if (act == CoercionAction.AsNull) {
return null;
}
if (act == CoercionAction.AsEmpty) {
return false;
}
text = text.trim();
final int len = text.length();
// For [databind#1852] allow some case-insensitive matches (namely,
// true/True/TRUE, false/False/FALSE
if (len == 4) {
if (_isTrue(text)) {
return true;
}
} else if (len == 5) {
if (_isFalse(text)) {
return false;
}
}
if (_checkTextualNull(ctxt, text)) {
return null;
}
return (Boolean) ctxt.handleWeirdStringValue(targetType, text,
"only \"true\" or \"false\" recognized");
}
protected final byte _parseBytePrimitive(JsonParser p, DeserializationContext ctxt)
throws IOException
{
String text;
switch (p.currentTokenId()) {
case JsonTokenId.ID_STRING:
text = p.getText();
break;
case JsonTokenId.ID_NUMBER_FLOAT:
CoercionAction act = _checkFloatToIntCoercion(p, ctxt, Byte.TYPE);
if (act == CoercionAction.AsNull) {
return (byte) 0;
}
if (act == CoercionAction.AsEmpty) {
return (byte) 0;
}
return p.getByteValue();
case JsonTokenId.ID_NUMBER_INT:
return p.getByteValue();
case JsonTokenId.ID_NULL:
_verifyNullForPrimitive(ctxt);
return (byte) 0;
// 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
case JsonTokenId.ID_START_OBJECT:
text = ctxt.extractScalarFromObject(p, this, Byte.TYPE);
break;
case JsonTokenId.ID_START_ARRAY: // unwrapping / from-empty-array coercion?
// 12-Jun-2020, tatu: For some reason calling `_deserializeFromArray()` won't work so:
if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
if (p.nextToken() == JsonToken.START_ARRAY) {
return (byte) handleNestedArrayForSingle(p, ctxt);
}
final byte parsed = _parseBytePrimitive(p, ctxt);
_verifyEndArrayForSingle(p, ctxt);
return parsed;
}
// fall through
default:
return ((Byte) ctxt.handleUnexpectedToken(ctxt.constructType(Byte.TYPE), p)).byteValue();
}
// Coercion from String
CoercionAction act = _checkFromStringCoercion(ctxt, text,
LogicalType.Integer, Byte.TYPE);
if (act == CoercionAction.AsNull) {
// 03-May-2021, tatu: Might not be allowed (should we do "empty" check?)
_verifyNullForPrimitive(ctxt);
return (byte) 0;
}
if (act == CoercionAction.AsEmpty) {
return (byte) 0;
}
text = text.trim();
if (_hasTextualNull(text)) {
_verifyNullForPrimitiveCoercion(ctxt, text);
return (byte) 0;
}
int value;
try {
value = NumberInput.parseInt(text);
} catch (IllegalArgumentException iae) {
return (Byte) ctxt.handleWeirdStringValue(_valueClass, text,
"not a valid `byte` value");
}
// So far so good: but does it fit? Allow both -128 / 255 range (inclusive)
if (_byteOverflow(value)) {
return (Byte) ctxt.handleWeirdStringValue(_valueClass, text,
"overflow, value cannot be represented as 8-bit value");
}
return (byte) value;
}
protected final short _parseShortPrimitive(JsonParser p, DeserializationContext ctxt)
throws IOException
{
String text;
switch (p.currentTokenId()) {
case JsonTokenId.ID_STRING:
text = p.getText();
break;
case JsonTokenId.ID_NUMBER_FLOAT:
CoercionAction act = _checkFloatToIntCoercion(p, ctxt, Short.TYPE);
if (act == CoercionAction.AsNull) {
return (short) 0;
}
if (act == CoercionAction.AsEmpty) {
return (short) 0;
}
return p.getShortValue();
case JsonTokenId.ID_NUMBER_INT:
return p.getShortValue();
case JsonTokenId.ID_NULL:
_verifyNullForPrimitive(ctxt);
return (short) 0;
// 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
case JsonTokenId.ID_START_OBJECT:
text = ctxt.extractScalarFromObject(p, this, Short.TYPE);
break;
case JsonTokenId.ID_START_ARRAY:
// 12-Jun-2020, tatu: For some reason calling `_deserializeFromArray()` won't work so:
if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
if (p.nextToken() == JsonToken.START_ARRAY) {
return (short) handleNestedArrayForSingle(p, ctxt);
}
final short parsed = _parseShortPrimitive(p, ctxt);
_verifyEndArrayForSingle(p, ctxt);
return parsed;
}
// fall through to fail
default:
return ((Short) ctxt.handleUnexpectedToken(ctxt.constructType(Short.TYPE), p)).shortValue();
}
CoercionAction act = _checkFromStringCoercion(ctxt, text,
LogicalType.Integer, Short.TYPE);
if (act == CoercionAction.AsNull) {
// 03-May-2021, tatu: Might not be allowed (should we do "empty" check?)
_verifyNullForPrimitive(ctxt);
return (short) 0;
}
if (act == CoercionAction.AsEmpty) {
return (short) 0;
}
text = text.trim();
if (_hasTextualNull(text)) {
_verifyNullForPrimitiveCoercion(ctxt, text);
return (short) 0;
}
int value;
try {
value = NumberInput.parseInt(text);
} catch (IllegalArgumentException iae) {
return (Short) ctxt.handleWeirdStringValue(Short.TYPE, text,
"not a valid `short` value");
}
if (_shortOverflow(value)) {
return (Short) ctxt.handleWeirdStringValue(Short.TYPE, text,
"overflow, value cannot be represented as 16-bit value");
}
return (short) value;
}
protected final int _parseIntPrimitive(JsonParser p, DeserializationContext ctxt)
throws IOException
{
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;
}
if (act == CoercionAction.AsEmpty) {
return 0;
}
return p.getValueAsInt();
case JsonTokenId.ID_NUMBER_INT:
return p.getIntValue();
case JsonTokenId.ID_NULL:
_verifyNullForPrimitive(ctxt);
return 0;
// 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
case JsonTokenId.ID_START_OBJECT:
text = ctxt.extractScalarFromObject(p, this, Integer.TYPE);
break;
case JsonTokenId.ID_START_ARRAY:
if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
if (p.nextToken() == JsonToken.START_ARRAY) {
return (int) handleNestedArrayForSingle(p, ctxt);
}
final int parsed = _parseIntPrimitive(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) {
// 03-May-2021, tatu: Might not be allowed (should we do "empty" check?)
_verifyNullForPrimitive(ctxt);
return 0;
}
if (act == CoercionAction.AsEmpty) {
return 0;
}
text = text.trim();
if (_hasTextualNull(text)) {
_verifyNullForPrimitiveCoercion(ctxt, text);
return 0;
}
return _parseIntPrimitive(ctxt, text);
}
/**
* @since 2.9
*/
protected final int _parseIntPrimitive(DeserializationContext ctxt, String text) throws IOException
{
try {
if (text.length() > 9) {
long l = NumberInput.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();
}
}
/**
* @since 2.12
*/
protected final Integer _parseInteger(JsonParser p, DeserializationContext ctxt,
Class> targetType)
throws IOException
{
String text;
switch (p.currentTokenId()) {
case JsonTokenId.ID_STRING:
text = p.getText();
break;
case JsonTokenId.ID_NUMBER_FLOAT: // coercing may work too
final CoercionAction act = _checkFloatToIntCoercion(p, ctxt, targetType);
if (act == CoercionAction.AsNull) {
return (Integer) getNullValue(ctxt);
}
if (act == CoercionAction.AsEmpty) {
return (Integer) getEmptyValue(ctxt);
}
return p.getValueAsInt();
case JsonTokenId.ID_NUMBER_INT: // NOTE: caller assumed to check in fast path
return p.getIntValue();
case JsonTokenId.ID_NULL: // null fine for non-primitive
return (Integer) getNullValue(ctxt);
// 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
case JsonTokenId.ID_START_OBJECT:
text = ctxt.extractScalarFromObject(p, this, targetType);
break;
case JsonTokenId.ID_START_ARRAY:
return (Integer) _deserializeFromArray(p, ctxt);
default:
return (Integer) ctxt.handleUnexpectedToken(getValueType(ctxt), p);
}
final CoercionAction act = _checkFromStringCoercion(ctxt, text);
if (act == CoercionAction.AsNull) {
return (Integer) getNullValue(ctxt);
}
if (act == CoercionAction.AsEmpty) {
return (Integer) getEmptyValue(ctxt);
}
text = text.trim();
if (_checkTextualNull(ctxt, text)) {
return (Integer) getNullValue(ctxt);
}
return _parseInteger(ctxt, text);
}
/**
* @since 2.14
*/
protected final Integer _parseInteger(DeserializationContext ctxt, String text) throws IOException
{
try {
if (text.length() > 9) {
long l = NumberInput.parseLong(text);
if (_intOverflow(l)) {
return (Integer) ctxt.handleWeirdStringValue(Integer.class, text,
"Overflow: numeric value (%s) out of range of `java.lang.Integer` (%d -%d)",
text, Integer.MIN_VALUE, Integer.MAX_VALUE);
}
return Integer.valueOf((int) l);
}
return NumberInput.parseInt(text);
} catch (IllegalArgumentException iae) {
return(Integer) ctxt.handleWeirdStringValue(Integer.class, text,
"not a valid `java.lang.Integer` value");
}
}
protected final long _parseLongPrimitive(JsonParser p, DeserializationContext ctxt)
throws IOException
{
String text;
switch (p.currentTokenId()) {
case JsonTokenId.ID_STRING:
text = p.getText();
break;
case JsonTokenId.ID_NUMBER_FLOAT:
final CoercionAction act = _checkFloatToIntCoercion(p, ctxt, Long.TYPE);
if (act == CoercionAction.AsNull) {
return 0L;
}
if (act == CoercionAction.AsEmpty) {
return 0L;
}
return p.getValueAsLong();
case JsonTokenId.ID_NUMBER_INT:
return p.getLongValue();
case JsonTokenId.ID_NULL:
_verifyNullForPrimitive(ctxt);
return 0L;
// 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
case JsonTokenId.ID_START_OBJECT:
text = ctxt.extractScalarFromObject(p, this, Long.TYPE);
break;
case JsonTokenId.ID_START_ARRAY:
if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
if (p.nextToken() == JsonToken.START_ARRAY) {
return (long) handleNestedArrayForSingle(p, ctxt);
}
final long parsed = _parseLongPrimitive(p, ctxt);
_verifyEndArrayForSingle(p, ctxt);
return parsed;
}
// fall through
default:
return ((Number) ctxt.handleUnexpectedToken(Long.TYPE, p)).longValue();
}
final CoercionAction act = _checkFromStringCoercion(ctxt, text,
LogicalType.Integer, Long.TYPE);
if (act == CoercionAction.AsNull) {
// 03-May-2021, tatu: Might not be allowed (should we do "empty" check?)
_verifyNullForPrimitive(ctxt);
return 0L;
}
if (act == CoercionAction.AsEmpty) {
return 0L;
}
text = text.trim();
if (_hasTextualNull(text)) {
_verifyNullForPrimitiveCoercion(ctxt, text);
return 0L;
}
return _parseLongPrimitive(ctxt, text);
}
/**
* @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(Long.TYPE, text,
"not a valid `long` value");
return _nonNullNumber(v).longValue();
}
}
/**
* @since 2.12
*/
protected final Long _parseLong(JsonParser p, DeserializationContext ctxt,
Class> targetType)
throws IOException
{
String text;
switch (p.currentTokenId()) {
case JsonTokenId.ID_STRING:
text = p.getText();
break;
case JsonTokenId.ID_NUMBER_FLOAT:
final CoercionAction act = _checkFloatToIntCoercion(p, ctxt, targetType);
if (act == CoercionAction.AsNull) {
return (Long) getNullValue(ctxt);
}
if (act == CoercionAction.AsEmpty) {
return (Long) getEmptyValue(ctxt);
}
return p.getValueAsLong();
case JsonTokenId.ID_NULL: // null fine for non-primitive
return (Long) getNullValue(ctxt);
case JsonTokenId.ID_NUMBER_INT:
return p.getLongValue();
// 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
case JsonTokenId.ID_START_OBJECT:
text = ctxt.extractScalarFromObject(p, this, targetType);
break;
case JsonTokenId.ID_START_ARRAY:
return (Long) _deserializeFromArray(p, ctxt);
default:
return (Long) ctxt.handleUnexpectedToken(getValueType(ctxt), p);
}
final CoercionAction act = _checkFromStringCoercion(ctxt, text);
if (act == CoercionAction.AsNull) {
return (Long) getNullValue(ctxt);
}
if (act == CoercionAction.AsEmpty) {
return (Long) getEmptyValue(ctxt);
}
text = text.trim();
if (_checkTextualNull(ctxt, text)) {
return (Long) getNullValue(ctxt);
}
// let's allow Strings to be converted too
return _parseLong(ctxt, text);
}
/**
* @since 2.14
*/
protected final Long _parseLong(DeserializationContext ctxt, String text) throws IOException
{
try {
return NumberInput.parseLong(text);
} catch (IllegalArgumentException iae) { }
return (Long) ctxt.handleWeirdStringValue(Long.class, text,
"not a valid `java.lang.Long` value");
}
protected final float _parseFloatPrimitive(JsonParser p, DeserializationContext ctxt)
throws IOException
{
String text;
switch (p.currentTokenId()) {
case JsonTokenId.ID_STRING:
text = p.getText();
break;
case JsonTokenId.ID_NUMBER_INT:
final CoercionAction act = _checkIntToFloatCoercion(p, ctxt, Float.TYPE);
if (act == CoercionAction.AsNull) {
return 0.0f;
}
if (act == CoercionAction.AsEmpty) {
return 0.0f;
}
// fall through to coerce
case JsonTokenId.ID_NUMBER_FLOAT:
return p.getFloatValue();
case JsonTokenId.ID_NULL:
_verifyNullForPrimitive(ctxt);
return 0f;
// 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
case JsonTokenId.ID_START_OBJECT:
text = ctxt.extractScalarFromObject(p, this, Float.TYPE);
break;
case JsonTokenId.ID_START_ARRAY:
if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
if (p.nextToken() == JsonToken.START_ARRAY) {
return (float) handleNestedArrayForSingle(p, ctxt);
}
final float parsed = _parseFloatPrimitive(p, ctxt);
_verifyEndArrayForSingle(p, ctxt);
return parsed;
}
// fall through
default:
return ((Number) ctxt.handleUnexpectedToken(Float.TYPE, p)).floatValue();
}
// 18-Nov-2020, tatu: Special case, Not-a-Numbers as String need to be
// considered "native" representation as JSON does not allow as numbers,
// and hence not bound by coercion rules
{
Float nan = _checkFloatSpecialValue(text);
if (nan != null) {
return nan.floatValue();
}
}
final CoercionAction act = _checkFromStringCoercion(ctxt, text,
LogicalType.Integer, Float.TYPE);
if (act == CoercionAction.AsNull) {
// 03-May-2021, tatu: Might not be allowed (should we do "empty" check?)
_verifyNullForPrimitive(ctxt);
return 0.0f;
}
if (act == CoercionAction.AsEmpty) {
return 0.0f;
}
text = text.trim();
if (_hasTextualNull(text)) {
_verifyNullForPrimitiveCoercion(ctxt, text);
return 0.0f;
}
return _parseFloatPrimitive(p, ctxt, text);
}
/**
* @since 2.9
*/
protected final float _parseFloatPrimitive(DeserializationContext ctxt, String text)
throws IOException
{
try {
return NumberInput.parseFloat(text);
} catch (IllegalArgumentException iae) { }
Number v = (Number) ctxt.handleWeirdStringValue(Float.TYPE, text,
"not a valid `float` value");
return _nonNullNumber(v).floatValue();
}
/**
* @since 2.14
*/
protected final float _parseFloatPrimitive(JsonParser p, DeserializationContext ctxt, String text)
throws IOException
{
try {
return NumberInput.parseFloat(text, p.isEnabled(StreamReadFeature.USE_FAST_DOUBLE_PARSER));
} catch (IllegalArgumentException iae) { }
Number v = (Number) ctxt.handleWeirdStringValue(Float.TYPE, text,
"not a valid `float` value");
return _nonNullNumber(v).floatValue();
}
/**
* Helper method called to check whether given String value contains one of
* "special" values (currently, NaN ("not-a-number") and plus/minus Infinity)
* and if so, returns that value; otherwise returns {@code null}.
*
* @param text String value to check
*
* @return One of {@link Float} constants referring to special value decoded,
* if value matched; {@code null} otherwise.
*
* @since 2.12
*/
protected Float _checkFloatSpecialValue(String text)
{
if (!text.isEmpty()) {
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;
default:
}
}
return null;
}
protected final double _parseDoublePrimitive(JsonParser p, DeserializationContext ctxt)
throws IOException
{
String text;
switch (p.currentTokenId()) {
case JsonTokenId.ID_STRING:
text = p.getText();
break;
case JsonTokenId.ID_NUMBER_INT:
final CoercionAction act = _checkIntToFloatCoercion(p, ctxt, Double.TYPE);
if (act == CoercionAction.AsNull) {
return 0.0d;
}
if (act == CoercionAction.AsEmpty) {
return 0.0d;
}
// fall through to coerce
case JsonTokenId.ID_NUMBER_FLOAT:
return p.getDoubleValue();
case JsonTokenId.ID_NULL:
_verifyNullForPrimitive(ctxt);
return 0.0;
// 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
case JsonTokenId.ID_START_OBJECT:
text = ctxt.extractScalarFromObject(p, this, Double.TYPE);
break;
case JsonTokenId.ID_START_ARRAY:
if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
if (p.nextToken() == JsonToken.START_ARRAY) {
return (double) handleNestedArrayForSingle(p, ctxt);
}
final double parsed = _parseDoublePrimitive(p, ctxt);
_verifyEndArrayForSingle(p, ctxt);
return parsed;
}
// fall through
default:
return ((Number) ctxt.handleUnexpectedToken(Double.TYPE, p)).doubleValue();
}
// 18-Nov-2020, tatu: Special case, Not-a-Numbers as String need to be
// considered "native" representation as JSON does not allow as numbers,
// and hence not bound by coercion rules
{
Double nan = this._checkDoubleSpecialValue(text);
if (nan != null) {
return nan.doubleValue();
}
}
final CoercionAction act = _checkFromStringCoercion(ctxt, text,
LogicalType.Integer, Double.TYPE);
if (act == CoercionAction.AsNull) {
// 03-May-2021, tatu: Might not be allowed (should we do "empty" check?)
_verifyNullForPrimitive(ctxt);
return 0.0;
}
if (act == CoercionAction.AsEmpty) {
return 0.0;
}
text = text.trim();
if (_hasTextualNull(text)) {
_verifyNullForPrimitiveCoercion(ctxt, text);
return 0.0;
}
return _parseDoublePrimitive(p, ctxt, text);
}
/**
* @since 2.9
*/
protected final double _parseDoublePrimitive(DeserializationContext ctxt, String text)
throws IOException
{
try {
return _parseDouble(text);
} catch (IllegalArgumentException iae) { }
Number v = (Number) ctxt.handleWeirdStringValue(Double.TYPE, text,
"not a valid `double` value (as String to convert)");
return _nonNullNumber(v).doubleValue();
}
/**
* @since 2.14
*/
protected final double _parseDoublePrimitive(JsonParser p, DeserializationContext ctxt, String text)
throws IOException
{
try {
return _parseDouble(text, p.isEnabled(StreamReadFeature.USE_FAST_DOUBLE_PARSER));
} catch (IllegalArgumentException iae) { }
Number v = (Number) ctxt.handleWeirdStringValue(Double.TYPE, text,
"not a valid `double` value (as String to convert)");
return _nonNullNumber(v).doubleValue();
}
/**
* 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.
* Does not use the new useFastParser
support.
*
* @see #_parseDouble(String, boolean)
*/
protected final static double _parseDouble(final String numStr) throws NumberFormatException
{
return _parseDouble(numStr, false);
}
/**
* 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.
*
* @since 2.14
*/
protected final static double _parseDouble(final String numStr, final boolean useFastParser) throws NumberFormatException
{
return NumberInput.parseDouble(numStr, useFastParser);
}
/**
* Helper method called to check whether given String value contains one of
* "special" values (currently, NaN ("not-a-number") and plus/minus Infinity)
* and if so, returns that value; otherwise returns {@code null}.
*
* @param text String value to check
*
* @return One of {@link Double} constants referring to special value decoded,
* if value matched; {@code null} otherwise.
*
* @since 2.12
*/
protected Double _checkDoubleSpecialValue(String text)
{
if (!text.isEmpty()) {
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;
default:
}
}
return null;
}
protected java.util.Date _parseDate(JsonParser p, DeserializationContext ctxt)
throws IOException
{
String text;
switch (p.currentTokenId()) {
case JsonTokenId.ID_STRING:
text = p.getText();
break;
case JsonTokenId.ID_NUMBER_INT:
{
long ts;
try {
ts = p.getLongValue();
// 16-Jan-2019, tatu: 2.10 uses InputCoercionException, earlier StreamReadException
} catch (StreamReadException e) {
Number v = (Number) ctxt.handleWeirdNumberValue(_valueClass, p.getNumberValue(),
"not a valid 64-bit `long` for creating `java.util.Date`");
ts = v.longValue();
}
return new java.util.Date(ts);
}
case JsonTokenId.ID_NULL:
return (java.util.Date) getNullValue(ctxt);
// 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
case JsonTokenId.ID_START_OBJECT:
text = ctxt.extractScalarFromObject(p, this, _valueClass);
break;
case JsonTokenId.ID_START_ARRAY:
return _parseDateFromArray(p, ctxt);
default:
return (java.util.Date) ctxt.handleUnexpectedToken(_valueClass, p);
}
return _parseDate(text.trim(), ctxt);
}
// @since 2.9
protected java.util.Date _parseDateFromArray(JsonParser p, DeserializationContext ctxt)
throws IOException
{
final CoercionAction act = _findCoercionFromEmptyArray(ctxt);
final boolean unwrap = ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
if (unwrap || (act != CoercionAction.Fail)) {
JsonToken t = p.nextToken();
if (t == JsonToken.END_ARRAY) {
switch (act) {
case AsEmpty:
return (java.util.Date) getEmptyValue(ctxt);
case AsNull:
case TryConvert:
return (java.util.Date) getNullValue(ctxt);
default:
}
} else if (unwrap) {
if (t == JsonToken.START_ARRAY) {
return (java.util.Date) handleNestedArrayForSingle(p, ctxt);
}
final Date parsed = _parseDate(p, ctxt);
_verifyEndArrayForSingle(p, ctxt);
return parsed;
}
}
return (java.util.Date) ctxt.handleUnexpectedToken(_valueClass, JsonToken.START_ARRAY, 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 (value.isEmpty()) {
final CoercionAction act = _checkFromStringCoercion(ctxt, value);
switch (act) { // note: Fail handled above
case AsEmpty:
return new java.util.Date(0L);
case AsNull:
case TryConvert:
default:
}
return null;
}
// 10-Jun-2020, tatu: Legacy handling from pre-2.12... should we still have it?
if (_hasTextualNull(value)) {
return null;
}
return ctxt.parseDate(value);
} catch (IllegalArgumentException iae) {
return (java.util.Date) ctxt.handleWeirdStringValue(_valueClass, value,
"not a valid representation (error: %s)",
ClassUtil.exceptionMessage(iae));
}
}
/**
* @since 2.1
* @deprecated Since 2.14 (use the non-deprecated overload)
*/
@Deprecated
protected final String _parseString(JsonParser p, DeserializationContext ctxt)
throws IOException
{
return _parseString(p, ctxt,
// Note: could consider passing `this`, but since this methods is
// often called for values "inside" type we are handling (like
// String array or Collection of Strings) that would probably
// not match. So default to "nulls as nulls" handler.
NullsConstantProvider.nuller());
}
/**
* Helper method used for deserializing String value, if possible, doing
* necessary conversion or throwing exception as necessary.
*
* Starting with 2.14 will also start checking {@code CoercionConfig}
* configuration to check whether coercion needed is allowed.
*
* @param p Currently active parser being iterated over
* @param ctxt Deserialization context
* @param nullProvider Entity we (only) need for case of secondary value
* type being coerced into {@code null}: if so, provider is asked for
* possible "null replacement" value.
*
* @since 2.14 (with this signature)
*/
protected final String _parseString(JsonParser p, DeserializationContext ctxt,
NullValueProvider nullProvider)
throws IOException
{
CoercionAction act = CoercionAction.TryConvert;
switch (p.currentTokenId()) {
case JsonTokenId.ID_STRING:
return p.getText();
// 07-Nov-2019, tatu: [databind#2535] Need to support byte[]->Base64 same as `StringDeserializer`
case JsonTokenId.ID_EMBEDDED_OBJECT:
Object ob = p.getEmbeddedObject();
if (ob instanceof byte[]) {
return ctxt.getBase64Variant().encode((byte[]) ob, false);
}
if (ob == null) {
return null;
}
// otherwise, try conversion using toString()...
return ob.toString();
// 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
case JsonTokenId.ID_START_OBJECT:
return ctxt.extractScalarFromObject(p, this, _valueClass);
case JsonTokenId.ID_NUMBER_INT:
act = _checkIntToStringCoercion(p, ctxt, _valueClass);
break;
case JsonTokenId.ID_NUMBER_FLOAT:
act = _checkFloatToStringCoercion(p, ctxt, _valueClass);
break;
case JsonTokenId.ID_TRUE:
case JsonTokenId.ID_FALSE:
act = _checkBooleanToStringCoercion(p, ctxt, _valueClass);
break;
}
if (act == CoercionAction.AsNull) {
return (String) nullProvider.getNullValue(ctxt);
}
if (act == CoercionAction.AsEmpty) {
return "";
}
// allow coercions for other scalar types
// 17-Jan-2018, tatu: Related to [databind#1853] avoid FIELD_NAME by ensuring it's
// "real" scalar
if (p.currentToken().isScalarValue()) {
String text = p.getValueAsString();
if (text != null) {
return text;
}
}
return (String) ctxt.handleUnexpectedToken(getValueType(ctxt), 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);
}
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); }
// @since 2.12
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 methods for sub-classes, new (2.12+)
/****************************************************
*/
/**
* @since 2.12
*/
protected CoercionAction _checkFromStringCoercion(DeserializationContext ctxt, String value)
throws IOException
{
return _checkFromStringCoercion(ctxt, value, logicalType(), handledType());
}
/**
* @since 2.12
*/
protected CoercionAction _checkFromStringCoercion(DeserializationContext ctxt, String value,
LogicalType logicalType, Class> rawTargetType)
throws IOException
{
// 18-Dec-2020, tatu: Formats without strong typing (XML, CSV, Properties at
// least) should allow from-String "coercion" since Strings are their
// native type.
// One open question is whether Empty/Blank String are special; they might
// be so only apply short-cut to other cases, for now
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 {
// 18-Dec-2020, tatu: As per above, allow for XML, CSV, Properties
if (ctxt.isEnabled(StreamReadCapability.UNTYPED_SCALARS)) {
return CoercionAction.TryConvert;
}
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());
}
}
return act;
}
/**
* @since 2.12
*/
protected 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;
}
/**
* @since 2.14
*/
protected CoercionAction _checkIntToStringCoercion(JsonParser p, DeserializationContext ctxt,
Class> rawTargetType)
throws IOException
{
return _checkToStringCoercion(p, ctxt, rawTargetType, p.getNumberValue(), CoercionInputShape.Integer);
}
/**
* @since 2.14
*/
protected CoercionAction _checkFloatToStringCoercion(JsonParser p, DeserializationContext ctxt,
Class> rawTargetType)
throws IOException
{
return _checkToStringCoercion(p, ctxt, rawTargetType, p.getNumberValue(), CoercionInputShape.Float);
}
/**
* @since 2.14
*/
protected CoercionAction _checkBooleanToStringCoercion(JsonParser p, DeserializationContext ctxt,
Class> rawTargetType)
throws IOException
{
return _checkToStringCoercion(p, ctxt, rawTargetType, p.getBooleanValue(), CoercionInputShape.Boolean);
}
/**
* @since 2.14
*/
protected CoercionAction _checkToStringCoercion(JsonParser p, DeserializationContext ctxt,
Class> rawTargetType, Object inputValue, CoercionInputShape inputShape)
throws IOException
{
final CoercionAction act = ctxt.findCoercionAction(LogicalType.Textual,
rawTargetType, inputShape);
if (act == CoercionAction.Fail) {
return _checkCoercionFail(ctxt, act, rawTargetType, inputValue,
inputShape.name() + " value (" + p.getText() + ")");
}
return act;
}
/**
* @since 2.14
*/
protected CoercionAction _checkIntToFloatCoercion(JsonParser p, DeserializationContext ctxt,
Class> rawTargetType)
throws IOException
{
final CoercionAction act = ctxt.findCoercionAction(LogicalType.Float,
rawTargetType, CoercionInputShape.Integer);
if (act == CoercionAction.Fail) {
return _checkCoercionFail(ctxt, act, rawTargetType, p.getNumberValue(),
"Integer value (" + p.getText() + ")");
}
return act;
}
/**
* @since 2.12
*/
protected Boolean _coerceBooleanFromInt(JsonParser p, DeserializationContext ctxt,
Class> rawTargetType)
throws IOException
{
CoercionAction act = ctxt.findCoercionAction(LogicalType.Boolean, rawTargetType, CoercionInputShape.Integer);
switch (act) {
case Fail:
_checkCoercionFail(ctxt, act, rawTargetType, p.getNumberValue(),
"Integer value ("+p.getText()+")");
return Boolean.FALSE;
case AsNull:
return null;
case AsEmpty:
return Boolean.FALSE;
default:
}
// 13-Oct-2016, tatu: As per [databind#1324], need to be careful wrt
// degenerate case of huge integers, legal in JSON.
// Also note that number tokens can not have WS to trim:
if (p.getNumberType() == NumberType.INT) {
// but minor optimization for common case is possible:
return p.getIntValue() != 0;
}
return !"0".equals(p.getText());
}
/**
* @since 2.12
*/
protected CoercionAction _checkCoercionFail(DeserializationContext ctxt,
CoercionAction act, Class> targetType, Object inputValue,
String inputDesc)
throws IOException
{
if (act == CoercionAction.Fail) {
ctxt.reportBadCoercion(this, targetType, inputValue,
"Cannot coerce %s to %s (but could if coercion was enabled using `CoercionConfig`)",
inputDesc, _coercedTypeDesc());
}
return act;
}
/**
* Method called when otherwise unrecognized String value is encountered for
* a non-primitive type: should see if it is String value {@code "null"}, and if so,
* whether it is acceptable according to configuration or not
*
* @since 2.12
*/
protected boolean _checkTextualNull(DeserializationContext ctxt, String text)
throws JsonMappingException
{
if (_hasTextualNull(text)) {
if (!ctxt.isEnabled(MapperFeature.ALLOW_COERCION_OF_SCALARS)) {
_reportFailedNullCoerce(ctxt, true, MapperFeature.ALLOW_COERCION_OF_SCALARS, "String \"null\"");
}
return true;
}
return false;
}
/*
/**********************************************************************
/* Helper methods for sub-classes, coercions, older (pre-2.12), non-deprecated
/**********************************************************************
*/
/**
* 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
{
if (ctxt.isEnabled(DeserializationFeature.USE_BIG_INTEGER_FOR_INTS)) {
return p.getBigIntegerValue();
}
if (ctxt.isEnabled(DeserializationFeature.USE_LONG_FOR_INTS)) {
return p.getLongValue();
}
return p.getNumberValue(); // should be optimal, whatever it is
}
/**
* Method called to verify that {@code null} token from input is acceptable
* for primitive (unboxed) target type. It should NOT be called if {@code null}
* was received by other means (coerced due to configuration, or even from
* optionally acceptable String {@code "null"} token).
*
* @since 2.9
*/
protected final void _verifyNullForPrimitive(DeserializationContext ctxt) 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());
}
}
/**
* Method called to verify that text value {@code "null"} from input is acceptable
* for primitive (unboxed) target type. It should not be called if actual
* {@code null} token was received, or if null is a result of coercion from
* Some other input type.
*
* @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);
}
protected void _reportFailedNullCoerce(DeserializationContext ctxt, boolean state, Enum> feature,
String inputDesc) throws JsonMappingException
{
String enableDesc = state ? "enable" : "disable";
ctxt.reportInputMismatch(this, "Cannot coerce %s to Null value as %s (%s `%s.%s` to allow)",
inputDesc, _coercedTypeDesc(), enableDesc, feature.getDeclaringClass().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.
*
* @return Message with backtick-enclosed name of type this deserializer supports
*
* @since 2.9
*/
protected String _coercedTypeDesc() {
boolean structured;
String typeDesc;
JavaType t = getValueType();
if ((t != null) && !t.isPrimitive()) {
structured = (t.isContainerType() || t.isReferenceType());
typeDesc = ClassUtil.getTypeDescription(t);
} else {
Class> cls = handledType();
structured = cls.isArray() || Collection.class.isAssignableFrom(cls)
|| Map.class.isAssignableFrom(cls);
typeDesc = ClassUtil.getClassDescription(cls);
}
if (structured) {
return "element of "+typeDesc;
}
return typeDesc+" value";
}
/*
/**********************************************************************
/* Helper methods for sub-classes, coercions, older (pre-2.12), deprecated
/**********************************************************************
*/
@Deprecated // since 2.12
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());
}
/**
* @deprecated Since 2.12 use {@link #_checkFromStringCoercion} instead
*/
@Deprecated
protected void _verifyStringForScalarCoercion(DeserializationContext ctxt, String str) throws JsonMappingException
{
MapperFeature feat = MapperFeature.ALLOW_COERCION_OF_SCALARS;
if (!ctxt.isEnabled(feat)) {
ctxt.reportInputMismatch(this, "Cannot coerce String \"%s\" to %s (enable `%s.%s` to allow)",
str, _coercedTypeDesc(), feat.getDeclaringClass().getSimpleName(), feat.name());
}
}
/**
* Method called when JSON String with value "" (that is, zero length) is encountered.
*
* @deprecated Since 2.12
*/
@Deprecated
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;
}
@Deprecated // since 2.12
protected void _failDoubleToIntCoercion(JsonParser p, DeserializationContext ctxt,
String type) throws IOException
{
ctxt.reportInputMismatch(handledType(),
"Cannot coerce a floating-point value ('%s') into %s (enable `DeserializationFeature.ACCEPT_FLOAT_AS_INT` to allow)",
p.getValueAsString(), type);
}
@Deprecated // since 2.12
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);
}
}
@Deprecated // since 2.12
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, "Cannot coerce Number (%s) to %s (enable `%s.%s` to allow)",
valueDesc, _coercedTypeDesc(), feat.getDeclaringClass().getSimpleName(), feat.name());
}
}
@Deprecated // since 2.12
protected Object _coerceNullToken(DeserializationContext ctxt, boolean isPrimitive) throws JsonMappingException
{
if (isPrimitive) {
_verifyNullForPrimitive(ctxt);
}
return getNullValue(ctxt);
}
@Deprecated // since 2.12
protected Object _coerceTextualNull(DeserializationContext ctxt, boolean isPrimitive) throws JsonMappingException {
if (!ctxt.isEnabled(MapperFeature.ALLOW_COERCION_OF_SCALARS)) {
_reportFailedNullCoerce(ctxt, true, MapperFeature.ALLOW_COERCION_OF_SCALARS, "String \"null\"");
}
return getNullValue(ctxt);
}
@Deprecated // since 2.12
protected boolean _isEmptyOrTextualNull(String value) {
return value.isEmpty() || "null".equals(value);
}
/*
/****************************************************
/* 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