1 package com.fasterxml.jackson.databind.deser.std; 2 3 import java.io.IOException; 4 5 import com.fasterxml.jackson.annotation.JsonFormat; 6 7 import com.fasterxml.jackson.core.*; 8 9 import com.fasterxml.jackson.databind.*; 10 import com.fasterxml.jackson.databind.annotation.JacksonStdImpl; 11 import com.fasterxml.jackson.databind.deser.ContextualDeserializer; 12 import com.fasterxml.jackson.databind.deser.SettableBeanProperty; 13 import com.fasterxml.jackson.databind.deser.ValueInstantiator; 14 import com.fasterxml.jackson.databind.introspect.AnnotatedMethod; 15 import com.fasterxml.jackson.databind.type.LogicalType; 16 import com.fasterxml.jackson.databind.util.ClassUtil; 17 import com.fasterxml.jackson.databind.util.CompactStringObjectMap; 18 import com.fasterxml.jackson.databind.util.EnumResolver; 19 20 /** 21 * Deserializer class that can deserialize instances of 22 * specified Enum class from Strings and Integers. 23 */ 24 @JacksonStdImpl // was missing until 2.6 25 public class EnumDeserializer 26 extends StdScalarDeserializer<Object> 27 implements ContextualDeserializer 28 { 29 private static final long serialVersionUID = 1L; 30 31 protected Object[] _enumsByIndex; 32 33 /** 34 * @since 2.8 35 */ 36 private final Enum<?> _enumDefaultValue; 37 38 /** 39 * @since 2.7.3 40 */ 41 protected final CompactStringObjectMap _lookupByName; 42 43 /** 44 * Alternatively, we may need a different lookup object if "use toString" 45 * is defined. 46 * 47 * @since 2.7.3 48 */ 49 protected CompactStringObjectMap _lookupByToString; 50 51 protected final Boolean _caseInsensitive; 52 53 /** 54 * @since 2.9 55 */ EnumDeserializer(EnumResolver byNameResolver, Boolean caseInsensitive)56 public EnumDeserializer(EnumResolver byNameResolver, Boolean caseInsensitive) 57 { 58 super(byNameResolver.getEnumClass()); 59 _lookupByName = byNameResolver.constructLookup(); 60 _enumsByIndex = byNameResolver.getRawEnums(); 61 _enumDefaultValue = byNameResolver.getDefaultValue(); 62 _caseInsensitive = caseInsensitive; 63 } 64 65 /** 66 * @since 2.9 67 */ EnumDeserializer(EnumDeserializer base, Boolean caseInsensitive)68 protected EnumDeserializer(EnumDeserializer base, Boolean caseInsensitive) 69 { 70 super(base); 71 _lookupByName = base._lookupByName; 72 _enumsByIndex = base._enumsByIndex; 73 _enumDefaultValue = base._enumDefaultValue; 74 _caseInsensitive = caseInsensitive; 75 } 76 77 /** 78 * @deprecated Since 2.9 79 */ 80 @Deprecated EnumDeserializer(EnumResolver byNameResolver)81 public EnumDeserializer(EnumResolver byNameResolver) { 82 this(byNameResolver, null); 83 } 84 85 /** 86 * @deprecated Since 2.8 87 */ 88 @Deprecated deserializerForCreator(DeserializationConfig config, Class<?> enumClass, AnnotatedMethod factory)89 public static JsonDeserializer<?> deserializerForCreator(DeserializationConfig config, 90 Class<?> enumClass, AnnotatedMethod factory) { 91 return deserializerForCreator(config, enumClass, factory, null, null); 92 } 93 94 /** 95 * Factory method used when Enum instances are to be deserialized 96 * using a creator (static factory method) 97 * 98 * @return Deserializer based on given factory method 99 * 100 * @since 2.8 101 */ deserializerForCreator(DeserializationConfig config, Class<?> enumClass, AnnotatedMethod factory, ValueInstantiator valueInstantiator, SettableBeanProperty[] creatorProps)102 public static JsonDeserializer<?> deserializerForCreator(DeserializationConfig config, 103 Class<?> enumClass, AnnotatedMethod factory, 104 ValueInstantiator valueInstantiator, SettableBeanProperty[] creatorProps) 105 { 106 if (config.canOverrideAccessModifiers()) { 107 ClassUtil.checkAndFixAccess(factory.getMember(), 108 config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS)); 109 } 110 return new FactoryBasedEnumDeserializer(enumClass, factory, 111 factory.getParameterType(0), 112 valueInstantiator, creatorProps); 113 } 114 115 /** 116 * Factory method used when Enum instances are to be deserialized 117 * using a zero-/no-args factory method 118 * 119 * @return Deserializer based on given no-args factory method 120 * 121 * @since 2.8 122 */ deserializerForNoArgsCreator(DeserializationConfig config, Class<?> enumClass, AnnotatedMethod factory)123 public static JsonDeserializer<?> deserializerForNoArgsCreator(DeserializationConfig config, 124 Class<?> enumClass, AnnotatedMethod factory) 125 { 126 if (config.canOverrideAccessModifiers()) { 127 ClassUtil.checkAndFixAccess(factory.getMember(), 128 config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS)); 129 } 130 return new FactoryBasedEnumDeserializer(enumClass, factory); 131 } 132 133 /** 134 * @since 2.9 135 */ withResolved(Boolean caseInsensitive)136 public EnumDeserializer withResolved(Boolean caseInsensitive) { 137 if (_caseInsensitive == caseInsensitive) { 138 return this; 139 } 140 return new EnumDeserializer(this, caseInsensitive); 141 } 142 143 @Override // since 2.9 createContextual(DeserializationContext ctxt, BeanProperty property)144 public JsonDeserializer<?> createContextual(DeserializationContext ctxt, 145 BeanProperty property) throws JsonMappingException 146 { 147 Boolean caseInsensitive = findFormatFeature(ctxt, property, handledType(), 148 JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); 149 if (caseInsensitive == null) { 150 caseInsensitive = _caseInsensitive; 151 } 152 return withResolved(caseInsensitive); 153 } 154 155 /* 156 /********************************************************** 157 /* Default JsonDeserializer implementation 158 /********************************************************** 159 */ 160 161 /** 162 * Because of costs associated with constructing Enum resolvers, 163 * let's cache instances by default. 164 */ 165 @Override isCachable()166 public boolean isCachable() { return true; } 167 168 @Override // since 2.12 logicalType()169 public LogicalType logicalType() { 170 return LogicalType.Enum; 171 } 172 173 @Override deserialize(JsonParser p, DeserializationContext ctxt)174 public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException 175 { 176 String text; 177 JsonToken curr = p.currentToken(); 178 179 // Usually should just get string value: 180 if (curr == JsonToken.VALUE_STRING || curr == JsonToken.FIELD_NAME) { 181 text = p.getText(); 182 183 // But let's consider int acceptable as well (if within ordinal range) 184 } else if (curr == JsonToken.VALUE_NUMBER_INT) { 185 // ... unless told not to do that 186 int index = p.getIntValue(); 187 if (ctxt.isEnabled(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)) { 188 return ctxt.handleWeirdNumberValue(_enumClass(), index, 189 "not allowed to deserialize Enum value out of number: disable DeserializationConfig.DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS to allow" 190 ); 191 } 192 if (index >= 0 && index < _enumsByIndex.length) { 193 return _enumsByIndex[index]; 194 } 195 if ((_enumDefaultValue != null) 196 && ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)) { 197 return _enumDefaultValue; 198 } 199 if (!ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) { 200 return ctxt.handleWeirdNumberValue(_enumClass(), index, 201 "index value outside legal index range [0..%s]", 202 _enumsByIndex.length-1); 203 } 204 return null; 205 } else if (curr == JsonToken.START_OBJECT) { 206 // 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML) 207 text = ctxt.extractScalarFromObject(p, this, _valueClass); 208 } else { 209 return _deserializeOther(p, ctxt); 210 } 211 212 CompactStringObjectMap lookup = ctxt.isEnabled(DeserializationFeature.READ_ENUMS_USING_TO_STRING) 213 ? _getToStringLookup(ctxt) : _lookupByName; 214 Object result = lookup.find(text); 215 if (result == null) { 216 String trimmed = text.trim(); 217 if ((trimmed == text) || (result = lookup.find(trimmed)) == null) { 218 return _deserializeAltString(p, ctxt, lookup, trimmed); 219 } 220 } 221 return result; 222 } 223 224 /* 225 /********************************************************** 226 /* Internal helper methods 227 /********************************************************** 228 */ 229 _deserializeAltString(JsonParser p, DeserializationContext ctxt, CompactStringObjectMap lookup, String name)230 private final Object _deserializeAltString(JsonParser p, DeserializationContext ctxt, 231 CompactStringObjectMap lookup, String name) throws IOException 232 { 233 name = name.trim(); 234 if (name.length() == 0) { 235 if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)) { 236 return getEmptyValue(ctxt); 237 } 238 } else { 239 // [databind#1313]: Case insensitive enum deserialization 240 if (Boolean.TRUE.equals(_caseInsensitive)) { 241 Object match = lookup.findCaseInsensitive(name); 242 if (match != null) { 243 return match; 244 } 245 } else if (!ctxt.isEnabled(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)) { 246 // [databind#149]: Allow use of 'String' indexes as well -- unless prohibited (as per above) 247 char c = name.charAt(0); 248 if (c >= '0' && c <= '9') { 249 try { 250 int index = Integer.parseInt(name); 251 if (!ctxt.isEnabled(MapperFeature.ALLOW_COERCION_OF_SCALARS)) { 252 return ctxt.handleWeirdStringValue(_enumClass(), name, 253 "value looks like quoted Enum index, but `MapperFeature.ALLOW_COERCION_OF_SCALARS` prevents use" 254 ); 255 } 256 if (index >= 0 && index < _enumsByIndex.length) { 257 return _enumsByIndex[index]; 258 } 259 } catch (NumberFormatException e) { 260 // fine, ignore, was not an integer 261 } 262 } 263 } 264 } 265 if ((_enumDefaultValue != null) 266 && ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)) { 267 return _enumDefaultValue; 268 } 269 if (!ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) { 270 return ctxt.handleWeirdStringValue(_enumClass(), name, 271 "not one of the values accepted for Enum class: %s", lookup.keys()); 272 } 273 return null; 274 } 275 _deserializeOther(JsonParser p, DeserializationContext ctxt)276 protected Object _deserializeOther(JsonParser p, DeserializationContext ctxt) throws IOException 277 { 278 // [databind#381] 279 if (p.hasToken(JsonToken.START_ARRAY)) { 280 return _deserializeFromArray(p, ctxt); 281 } 282 return ctxt.handleUnexpectedToken(_enumClass(), p); 283 } 284 _enumClass()285 protected Class<?> _enumClass() { 286 return handledType(); 287 } 288 _getToStringLookup(DeserializationContext ctxt)289 protected CompactStringObjectMap _getToStringLookup(DeserializationContext ctxt) 290 { 291 CompactStringObjectMap lookup = _lookupByToString; 292 // note: exact locking not needed; all we care for here is to try to 293 // reduce contention for the initial resolution 294 if (lookup == null) { 295 synchronized (this) { 296 lookup = EnumResolver.constructUnsafeUsingToString(_enumClass(), 297 ctxt.getAnnotationIntrospector()) 298 .constructLookup(); 299 } 300 _lookupByToString = lookup; 301 } 302 return lookup; 303 } 304 } 305