package com.fasterxml.jackson.databind.deser.std;

import java.io.IOException;
import java.util.Objects;

import com.fasterxml.jackson.annotation.JsonFormat;

import com.fasterxml.jackson.core.*;

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.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
import com.fasterxml.jackson.databind.deser.ValueInstantiator;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
import com.fasterxml.jackson.databind.type.LogicalType;
import com.fasterxml.jackson.databind.util.ClassUtil;
import com.fasterxml.jackson.databind.util.CompactStringObjectMap;
import com.fasterxml.jackson.databind.util.EnumResolver;

Deserializer class that can deserialize instances of specified Enum class from Strings and Integers.
/** * Deserializer class that can deserialize instances of * specified Enum class from Strings and Integers. */
@JacksonStdImpl // was missing until 2.6 public class EnumDeserializer extends StdScalarDeserializer<Object> implements ContextualDeserializer { private static final long serialVersionUID = 1L; protected Object[] _enumsByIndex;
Since:2.8
/** * @since 2.8 */
private final Enum<?> _enumDefaultValue;
Since:2.7.3
/** * @since 2.7.3 */
protected final CompactStringObjectMap _lookupByName;
Alternatively, we may need a different lookup object if "use toString" is defined.
Since:2.7.3
/** * Alternatively, we may need a different lookup object if "use toString" * is defined. * * @since 2.7.3 */
protected CompactStringObjectMap _lookupByToString; protected final Boolean _caseInsensitive;
Marker flag for cases where we expect actual integral value for Enum, based on @JsonValue (and equivalent) annotated accessor.
Since:2.13
/** * Marker flag for cases where we expect actual integral value for Enum, * based on {@code @JsonValue} (and equivalent) annotated accessor. * * @since 2.13 */
protected final boolean _isFromIntValue;
Since:2.9
/** * @since 2.9 */
public EnumDeserializer(EnumResolver byNameResolver, Boolean caseInsensitive) { super(byNameResolver.getEnumClass()); _lookupByName = byNameResolver.constructLookup(); _enumsByIndex = byNameResolver.getRawEnums(); _enumDefaultValue = byNameResolver.getDefaultValue(); _caseInsensitive = caseInsensitive; _isFromIntValue = byNameResolver.isFromIntValue(); }
Since:2.9
/** * @since 2.9 */
protected EnumDeserializer(EnumDeserializer base, Boolean caseInsensitive) { super(base); _lookupByName = base._lookupByName; _enumsByIndex = base._enumsByIndex; _enumDefaultValue = base._enumDefaultValue; _caseInsensitive = caseInsensitive; _isFromIntValue = base._isFromIntValue; }
Deprecated:Since 2.9
/** * @deprecated Since 2.9 */
@Deprecated public EnumDeserializer(EnumResolver byNameResolver) { this(byNameResolver, null); }
Deprecated:Since 2.8
/** * @deprecated Since 2.8 */
@Deprecated public static JsonDeserializer<?> deserializerForCreator(DeserializationConfig config, Class<?> enumClass, AnnotatedMethod factory) { return deserializerForCreator(config, enumClass, factory, null, null); }
Factory method used when Enum instances are to be deserialized using a creator (static factory method)
Returns:Deserializer based on given factory method
Since:2.8
/** * Factory method used when Enum instances are to be deserialized * using a creator (static factory method) * * @return Deserializer based on given factory method * * @since 2.8 */
public static JsonDeserializer<?> deserializerForCreator(DeserializationConfig config, Class<?> enumClass, AnnotatedMethod factory, ValueInstantiator valueInstantiator, SettableBeanProperty[] creatorProps) { if (config.canOverrideAccessModifiers()) { ClassUtil.checkAndFixAccess(factory.getMember(), config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS)); } return new FactoryBasedEnumDeserializer(enumClass, factory, factory.getParameterType(0), valueInstantiator, creatorProps); }
Factory method used when Enum instances are to be deserialized using a zero-/no-args factory method
Returns:Deserializer based on given no-args factory method
Since:2.8
/** * Factory method used when Enum instances are to be deserialized * using a zero-/no-args factory method * * @return Deserializer based on given no-args factory method * * @since 2.8 */
public static JsonDeserializer<?> deserializerForNoArgsCreator(DeserializationConfig config, Class<?> enumClass, AnnotatedMethod factory) { if (config.canOverrideAccessModifiers()) { ClassUtil.checkAndFixAccess(factory.getMember(), config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS)); } return new FactoryBasedEnumDeserializer(enumClass, factory); }
Since:2.9
/** * @since 2.9 */
public EnumDeserializer withResolved(Boolean caseInsensitive) { if (Objects.equals(_caseInsensitive, caseInsensitive)) { return this; } return new EnumDeserializer(this, caseInsensitive); } @Override // since 2.9 public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException { Boolean caseInsensitive = findFormatFeature(ctxt, property, handledType(), JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); if (caseInsensitive == null) { caseInsensitive = _caseInsensitive; } return withResolved(caseInsensitive); } /* /********************************************************** /* Default JsonDeserializer implementation /********************************************************** */
Because of costs associated with constructing Enum resolvers, let's cache instances by default.
/** * Because of costs associated with constructing Enum resolvers, * let's cache instances by default. */
@Override public boolean isCachable() { return true; } @Override // since 2.12 public LogicalType logicalType() { return LogicalType.Enum; } @Override // since 2.12 public Object getEmptyValue(DeserializationContext ctxt) throws JsonMappingException { return _enumDefaultValue; } @Override public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { // Usually should just get string value: // 04-Sep-2020, tatu: for 2.11.3 / 2.12.0, removed "FIELD_NAME" as allowed; // did not work and gave odd error message. if (p.hasToken(JsonToken.VALUE_STRING)) { return _fromString(p, ctxt, p.getText()); } // But let's consider int acceptable as well (if within ordinal range) if (p.hasToken(JsonToken.VALUE_NUMBER_INT)) { // 26-Sep-2021, tatu: [databind#1850] Special case where we get "true" integer // enumeration and should avoid use of {@code Enum.index()} if (_isFromIntValue) { // ... whether to rely on "getText()" returning String, or get number, convert? // For now assume all format backends can produce String: return _fromString(p, ctxt, p.getText()); } return _fromInteger(p, ctxt, p.getIntValue()); } // 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML) if (p.isExpectedStartObjectToken()) { return _fromString(p, ctxt, ctxt.extractScalarFromObject(p, this, _valueClass)); } return _deserializeOther(p, ctxt); } protected Object _fromString(JsonParser p, DeserializationContext ctxt, String text) throws IOException { CompactStringObjectMap lookup = ctxt.isEnabled(DeserializationFeature.READ_ENUMS_USING_TO_STRING) ? _getToStringLookup(ctxt) : _lookupByName; Object result = lookup.find(text); if (result == null) { String trimmed = text.trim(); if ((trimmed == text) || (result = lookup.find(trimmed)) == null) { return _deserializeAltString(p, ctxt, lookup, trimmed); } } return result; } protected Object _fromInteger(JsonParser p, DeserializationContext ctxt, int index) throws IOException { final CoercionAction act = ctxt.findCoercionAction(logicalType(), handledType(), CoercionInputShape.Integer); // First, check legacy setting for slightly different message if (act == CoercionAction.Fail) { if (ctxt.isEnabled(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)) { return ctxt.handleWeirdNumberValue(_enumClass(), index, "not allowed to deserialize Enum value out of number: disable DeserializationConfig.DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS to allow" ); } // otherwise this will force failure with new setting _checkCoercionFail(ctxt, act, handledType(), index, "Integer value ("+index+")"); } switch (act) { case AsNull: return null; case AsEmpty: return getEmptyValue(ctxt); case TryConvert: default: } if (index >= 0 && index < _enumsByIndex.length) { return _enumsByIndex[index]; } if ((_enumDefaultValue != null) && ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)) { return _enumDefaultValue; } if (!ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) { return ctxt.handleWeirdNumberValue(_enumClass(), index, "index value outside legal index range [0..%s]", _enumsByIndex.length-1); } return null; } /* return _checkCoercionFail(ctxt, act, rawTargetType, value, "empty String (\"\")"); */ /* /********************************************************** /* Internal helper methods /********************************************************** */ private final Object _deserializeAltString(JsonParser p, DeserializationContext ctxt, CompactStringObjectMap lookup, String nameOrig) throws IOException { String name = nameOrig.trim(); if (name.isEmpty()) { // empty or blank // 07-Jun-2021, tatu: [databind#3171] Need to consider Default value first // (alas there's bit of duplication here) if ((_enumDefaultValue != null) && ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)) { return _enumDefaultValue; } if (ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) { return null; } CoercionAction act; if (nameOrig.isEmpty()) { act = _findCoercionFromEmptyString(ctxt); act = _checkCoercionFail(ctxt, act, handledType(), nameOrig, "empty String (\"\")"); } else { act = _findCoercionFromBlankString(ctxt); act = _checkCoercionFail(ctxt, act, handledType(), nameOrig, "blank String (all whitespace)"); } switch (act) { case AsEmpty: case TryConvert: return getEmptyValue(ctxt); case AsNull: default: // Fail already handled earlier } return null; // if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)) { } else { // [databind#1313]: Case insensitive enum deserialization if (Boolean.TRUE.equals(_caseInsensitive)) { Object match = lookup.findCaseInsensitive(name); if (match != null) { return match; } } else if (!ctxt.isEnabled(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS) && !_isFromIntValue) { // [databind#149]: Allow use of 'String' indexes as well -- unless prohibited (as per above) char c = name.charAt(0); if (c >= '0' && c <= '9') { try { int index = Integer.parseInt(name); if (!ctxt.isEnabled(MapperFeature.ALLOW_COERCION_OF_SCALARS)) { return ctxt.handleWeirdStringValue(_enumClass(), name, "value looks like quoted Enum index, but `MapperFeature.ALLOW_COERCION_OF_SCALARS` prevents use" ); } if (index >= 0 && index < _enumsByIndex.length) { return _enumsByIndex[index]; } } catch (NumberFormatException e) { // fine, ignore, was not an integer } } } } if ((_enumDefaultValue != null) && ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)) { return _enumDefaultValue; } if (ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) { return null; } return ctxt.handleWeirdStringValue(_enumClass(), name, "not one of the values accepted for Enum class: %s", lookup.keys()); } protected Object _deserializeOther(JsonParser p, DeserializationContext ctxt) throws IOException { // [databind#381] if (p.hasToken(JsonToken.START_ARRAY)) { return _deserializeFromArray(p, ctxt); } return ctxt.handleUnexpectedToken(_enumClass(), p); } protected Class<?> _enumClass() { return handledType(); } protected CompactStringObjectMap _getToStringLookup(DeserializationContext ctxt) { CompactStringObjectMap lookup = _lookupByToString; // note: exact locking not needed; all we care for here is to try to // reduce contention for the initial resolution if (lookup == null) { synchronized (this) { lookup = EnumResolver.constructUsingToString(ctxt.getConfig(), _enumClass()) .constructLookup(); } _lookupByToString = lookup; } return lookup; } }