package com.fasterxml.jackson.databind.deser.std; import java.io.IOException; 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.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.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. */ @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 */ private final Enum<?> _enumDefaultValue; /** * @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 */ protected CompactStringObjectMap _lookupByToString; protected final Boolean _caseInsensitive; /** * @since 2.9 */ public EnumDeserializer(EnumResolver byNameResolver, Boolean caseInsensitive) { super(byNameResolver.getEnumClass()); _lookupByName = byNameResolver.constructLookup(); _enumsByIndex = byNameResolver.getRawEnums(); _enumDefaultValue = byNameResolver.getDefaultValue(); _caseInsensitive = caseInsensitive; } /** * @since 2.9 */ protected EnumDeserializer(EnumDeserializer base, Boolean caseInsensitive) { super(base); _lookupByName = base._lookupByName; _enumsByIndex = base._enumsByIndex; _enumDefaultValue = base._enumDefaultValue; _caseInsensitive = caseInsensitive; } /** * @deprecated Since 2.9 */ @Deprecated public EnumDeserializer(EnumResolver byNameResolver) { this(byNameResolver, null); } /** * @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) * * @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 * * @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 */ public EnumDeserializer withResolved(Boolean caseInsensitive) { if (_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. */ @Override public boolean isCachable() { return true; } @Override public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { JsonToken curr = p.getCurrentToken(); // Usually should just get string value: if (curr == JsonToken.VALUE_STRING || curr == JsonToken.FIELD_NAME) { CompactStringObjectMap lookup = ctxt.isEnabled(DeserializationFeature.READ_ENUMS_USING_TO_STRING) ? _getToStringLookup(ctxt) : _lookupByName; final String name = p.getText(); Object result = lookup.find(name); if (result == null) { return _deserializeAltString(p, ctxt, lookup, name); } return result; } // But let's consider int acceptable as well (if within ordinal range) if (curr == JsonToken.VALUE_NUMBER_INT) { // ... unless told not to do that int index = p.getIntValue(); 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" ); } 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 _deserializeOther(p, ctxt); } /* /********************************************************** /* Internal helper methods /********************************************************** */ private final Object _deserializeAltString(JsonParser p, DeserializationContext ctxt, CompactStringObjectMap lookup, String name) throws IOException { name = name.trim(); if (name.length() == 0) { if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)) { return getEmptyValue(ctxt); } } 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)) { // [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 (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 ctxt.handleWeirdStringValue(_enumClass(), name, "value not one of declared Enum instance names: %s", lookup.keys()); } return null; } 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.constructUnsafeUsingToString(_enumClass(), ctxt.getAnnotationIntrospector()) .constructLookup(); } _lookupByToString = lookup; } return lookup; } }