Skip to content

Commit 85edd63

Browse files
ILGO0413cowtowncoder
authored andcommitted
Fix #2873: Added case sensitivity check flag from MapperConfig to EnumResolver
1 parent 960f4f6 commit 85edd63

File tree

9 files changed

+215
-75
lines changed

9 files changed

+215
-75
lines changed

release-notes/CREDITS-2.x

+4
Original file line numberDiff line numberDiff line change
@@ -1221,3 +1221,7 @@ Jendrik Johannes (jjohannes@github)
12211221
Swayam Raina (swayamraina@github)
12221222
* Contributed #2761: Support multiple names in `JsonSubType.Type`
12231223
(2.12.0)
1224+
1225+
Ilya Golovin (ilgo0413@github)
1226+
* Contributed #2873: `MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS` should work for enum as keys
1227+
(2.12.0)

release-notes/VERSION-2.x

+2
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ Project: jackson-databind
6666
#2800: Extract getter/setter/field name mangling from `BeanUtil` into
6767
pluggable `AccessorNamingStrategy`
6868
#2805: Remove `JsonProcessingException` from `ObjectMapper.treeToValue()`
69+
#2873: `MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS` should work for enum as keys
70+
(fix contributed by Ilya G)
6971
- Add `BeanDeserializerBase.isCaseInsensitive()`
7072
- Some refactoring of `CollectionDeserializer` to solve CSV array handling issues
7173

src/main/java/com/fasterxml/jackson/databind/MapperFeature.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,10 @@ public enum MapperFeature implements ConfigFeature
403403
* If enabled, Enum deserialization will ignore case, that is, case of incoming String
404404
* value and enum id (depending on other settings, either `name()`, `toString()`, or
405405
* explicit override) do not need to match.
406-
* <p>
406+
*<p>
407+
* This should allow both Enum-as-value deserialization and Enum-as-Map-key, but latter
408+
* only works since Jackson 2.12 (due to incomplete implementation).
409+
*<p>
407410
* Feature is disabled by default.
408411
*
409412
* @since 2.9

src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -2370,12 +2370,11 @@ protected EnumResolver constructEnumResolver(Class<?> enumClass,
23702370
ClassUtil.checkAndFixAccess(jsonValueAccessor.getMember(),
23712371
config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS));
23722372
}
2373-
return EnumResolver.constructUnsafeUsingMethod(enumClass,
2374-
jsonValueAccessor, config.getAnnotationIntrospector());
2373+
return EnumResolver.constructUsingMethod(config, enumClass, jsonValueAccessor);
23752374
}
23762375
// 14-Mar-2016, tatu: We used to check `DeserializationFeature.READ_ENUMS_USING_TO_STRING`
23772376
// here, but that won't do: it must be dynamically changeable...
2378-
return EnumResolver.constructUnsafe(enumClass, config.getAnnotationIntrospector());
2377+
return EnumResolver.constructFor(config, enumClass);
23792378
}
23802379

23812380
/**

src/main/java/com/fasterxml/jackson/databind/deser/std/EnumDeserializer.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -294,8 +294,7 @@ protected CompactStringObjectMap _getToStringLookup(DeserializationContext ctxt)
294294
// reduce contention for the initial resolution
295295
if (lookup == null) {
296296
synchronized (this) {
297-
lookup = EnumResolver.constructUnsafeUsingToString(_enumClass(),
298-
ctxt.getAnnotationIntrospector())
297+
lookup = EnumResolver.constructUsingToString(ctxt.getConfig(), _enumClass())
299298
.constructLookup();
300299
}
301300
_lookupByToString = lookup;

src/main/java/com/fasterxml/jackson/databind/deser/std/StdKeyDeserializer.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -410,8 +410,8 @@ private EnumResolver _getToStringResolver(DeserializationContext ctxt)
410410
EnumResolver res = _byToStringResolver;
411411
if (res == null) {
412412
synchronized (this) {
413-
res = EnumResolver.constructUnsafeUsingToString(_byNameResolver.getEnumClass(),
414-
ctxt.getAnnotationIntrospector());
413+
res = EnumResolver.constructUsingToString(ctxt.getConfig(),
414+
_byNameResolver.getEnumClass());
415415
_byToStringResolver = res;
416416
}
417417
}

src/main/java/com/fasterxml/jackson/databind/util/EnumResolver.java

+176-65
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import java.util.*;
44

55
import com.fasterxml.jackson.databind.AnnotationIntrospector;
6+
import com.fasterxml.jackson.databind.DeserializationConfig;
7+
import com.fasterxml.jackson.databind.MapperFeature;
68
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
79

810
/**
@@ -21,31 +23,51 @@ public class EnumResolver implements java.io.Serializable
2123

2224
protected final Enum<?> _defaultValue;
2325

26+
/**
27+
* @since 2.12
28+
*/
29+
protected final boolean _isIgnoreCase;
30+
31+
/**
32+
* @since 2.12
33+
*/
2434
protected EnumResolver(Class<Enum<?>> enumClass, Enum<?>[] enums,
25-
HashMap<String, Enum<?>> map, Enum<?> defaultValue)
35+
HashMap<String, Enum<?>> map, Enum<?> defaultValue,
36+
boolean isIgnoreCase)
2637
{
2738
_enumClass = enumClass;
2839
_enums = enums;
2940
_enumsById = map;
3041
_defaultValue = defaultValue;
42+
_isIgnoreCase = isIgnoreCase;
3143
}
3244

3345
/**
3446
* Factory method for constructing resolver that maps from Enum.name() into
35-
* Enum value
47+
* Enum value.
48+
*
49+
* @since 2.12
50+
*/
51+
public static EnumResolver constructFor(DeserializationConfig config,
52+
Class<?> enumCls) {
53+
return _constructFor(enumCls, config.getAnnotationIntrospector(),
54+
config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS));
55+
}
56+
57+
/**
58+
* @since 2.12
3659
*/
37-
public static EnumResolver constructFor(Class<Enum<?>> enumCls, AnnotationIntrospector ai)
60+
protected static EnumResolver _constructFor(Class<?> enumCls0,
61+
AnnotationIntrospector ai, boolean isIgnoreCase)
3862
{
39-
Enum<?>[] enumValues = enumCls.getEnumConstants();
40-
if (enumValues == null) {
41-
throw new IllegalArgumentException("No enum constants for class "+enumCls.getName());
42-
}
43-
String[] names = ai.findEnumValues(enumCls, enumValues, new String[enumValues.length]);
63+
final Class<Enum<?>> enumCls = _enumClass(enumCls0);
64+
final Enum<?>[] enumConstants = _enumConstants(enumCls0);
65+
String[] names = ai.findEnumValues(enumCls, enumConstants, new String[enumConstants.length]);
4466
final String[][] allAliases = new String[names.length][];
45-
ai.findEnumAliases(enumCls, enumValues, allAliases);
67+
ai.findEnumAliases(enumCls, enumConstants, allAliases);
4668
HashMap<String, Enum<?>> map = new HashMap<String, Enum<?>>();
47-
for (int i = 0, len = enumValues.length; i < len; ++i) {
48-
final Enum<?> enumValue = enumValues[i];
69+
for (int i = 0, len = enumConstants.length; i < len; ++i) {
70+
final Enum<?> enumValue = enumConstants[i];
4971
String name = names[i];
5072
if (name == null) {
5173
name = enumValue.name();
@@ -62,27 +84,30 @@ public static EnumResolver constructFor(Class<Enum<?>> enumCls, AnnotationIntros
6284
}
6385
}
6486
}
65-
return new EnumResolver(enumCls, enumValues, map, ai.findDefaultEnumValue(enumCls));
87+
return new EnumResolver(enumCls, enumConstants, map,
88+
_enumDefault(ai, enumCls), isIgnoreCase);
6689
}
6790

6891
/**
69-
* @deprecated Since 2.8, use {@link #constructUsingToString(Class, AnnotationIntrospector)} instead
92+
* Factory method for constructing resolver that maps from Enum.toString() into
93+
* Enum value
94+
*
95+
* @since 2.12
7096
*/
71-
@Deprecated
72-
public static EnumResolver constructUsingToString(Class<Enum<?>> enumCls) {
73-
return constructUsingToString(enumCls, null);
97+
public static EnumResolver constructUsingToString(DeserializationConfig config,
98+
Class<?> enumCls) {
99+
return _constructUsingToString(enumCls, config.getAnnotationIntrospector(),
100+
config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS));
74101
}
75102

76103
/**
77-
* Factory method for constructing resolver that maps from Enum.toString() into
78-
* Enum value
79-
*
80-
* @since 2.8
104+
* @since 2.12
81105
*/
82-
public static EnumResolver constructUsingToString(Class<Enum<?>> enumCls,
83-
AnnotationIntrospector ai)
106+
protected static EnumResolver _constructUsingToString(Class<?> enumCls0,
107+
AnnotationIntrospector ai, boolean isIgnoreCase)
84108
{
85-
Enum<?>[] enumConstants = enumCls.getEnumConstants();
109+
final Class<Enum<?>> enumCls = _enumClass(enumCls0);
110+
final Enum<?>[] enumConstants = _enumConstants(enumCls0);
86111
HashMap<String, Enum<?>> map = new HashMap<String, Enum<?>>();
87112
final String[][] allAliases = new String[enumConstants.length][];
88113
ai.findEnumAliases(enumCls, enumConstants, allAliases);
@@ -102,21 +127,34 @@ public static EnumResolver constructUsingToString(Class<Enum<?>> enumCls,
102127
}
103128
}
104129
}
105-
return new EnumResolver(enumCls, enumConstants, map, ai.findDefaultEnumValue(enumCls));
130+
return new EnumResolver(enumCls, enumConstants, map,
131+
_enumDefault(ai, enumCls), isIgnoreCase);
106132
}
107133

108134
/**
109-
* @since 2.9
135+
* Method used when actual String serialization is indicated using @JsonValue
136+
* on a method in Enum class.
137+
*
138+
* @since 2.12
110139
*/
111-
public static EnumResolver constructUsingMethod(Class<Enum<?>> enumCls,
112-
AnnotatedMember accessor,
113-
AnnotationIntrospector ai)
140+
public static EnumResolver constructUsingMethod(DeserializationConfig config,
141+
Class<?> enumCls, AnnotatedMember accessor) {
142+
return _constructUsingMethod(enumCls, accessor, config.getAnnotationIntrospector(),
143+
config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS));
144+
}
145+
146+
/**
147+
* @since 2.12
148+
*/
149+
protected static EnumResolver _constructUsingMethod(Class<?> enumCls0,
150+
AnnotatedMember accessor, AnnotationIntrospector ai, boolean isIgnoreCase)
114151
{
115-
Enum<?>[] enumValues = enumCls.getEnumConstants();
152+
final Class<Enum<?>> enumCls = _enumClass(enumCls0);
153+
final Enum<?>[] enumConstants = _enumConstants(enumCls0);
116154
HashMap<String, Enum<?>> map = new HashMap<String, Enum<?>>();
117155
// from last to first, so that in case of duplicate values, first wins
118-
for (int i = enumValues.length; --i >= 0; ) {
119-
Enum<?> en = enumValues[i];
156+
for (int i = enumConstants.length; --i >= 0; ) {
157+
Enum<?> en = enumConstants[i];
120158
try {
121159
Object o = accessor.getValue(en);
122160
if (o != null) {
@@ -126,60 +164,133 @@ public static EnumResolver constructUsingMethod(Class<Enum<?>> enumCls,
126164
throw new IllegalArgumentException("Failed to access @JsonValue of Enum value "+en+": "+e.getMessage());
127165
}
128166
}
129-
Enum<?> defaultEnum = (ai != null) ? ai.findDefaultEnumValue(enumCls) : null;
130-
return new EnumResolver(enumCls, enumValues, map, defaultEnum);
167+
return new EnumResolver(enumCls, enumConstants, map,
168+
_enumDefault(ai, enumCls), isIgnoreCase);
131169
}
132170

171+
public CompactStringObjectMap constructLookup() {
172+
return CompactStringObjectMap.construct(_enumsById);
173+
}
174+
175+
@SuppressWarnings("unchecked")
176+
protected static Class<Enum<?>> _enumClass(Class<?> enumCls0) {
177+
return (Class<Enum<?>>) enumCls0;
178+
}
179+
180+
protected static Enum<?>[] _enumConstants(Class<?> enumCls) {
181+
final Enum<?>[] enumValues = _enumClass(enumCls).getEnumConstants();
182+
if (enumValues == null) {
183+
throw new IllegalArgumentException("No enum constants for class "+enumCls.getName());
184+
}
185+
return enumValues;
186+
}
187+
188+
protected static Enum<?> _enumDefault(AnnotationIntrospector intr, Class<?> enumCls) {
189+
return (intr != null) ? intr.findDefaultEnumValue(_enumClass(enumCls)) : null;
190+
}
191+
192+
/*
193+
/**********************************************************************
194+
/* Deprecated constructors, factory methods
195+
/**********************************************************************
196+
*/
197+
133198
/**
134-
* This method is needed because of the dynamic nature of constructing Enum
135-
* resolvers.
199+
* @deprecated Since 2.12 (remove from 2.13+ not part of public API)
136200
*/
137-
@SuppressWarnings({ "unchecked" })
138-
public static EnumResolver constructUnsafe(Class<?> rawEnumCls, AnnotationIntrospector ai)
139-
{
140-
/* This is oh so wrong... but at least ugliness is mostly hidden in just
141-
* this one place.
142-
*/
143-
Class<Enum<?>> enumCls = (Class<Enum<?>>) rawEnumCls;
144-
return constructFor(enumCls, ai);
201+
@Deprecated // since 2.12
202+
protected EnumResolver(Class<Enum<?>> enumClass, Enum<?>[] enums,
203+
HashMap<String, Enum<?>> map, Enum<?> defaultValue) {
204+
this(enumClass, enums, map, defaultValue, false);
205+
}
206+
207+
/**
208+
* @deprecated Since 2.12
209+
*/
210+
@Deprecated // since 2.12
211+
public static EnumResolver constructFor(Class<Enum<?>> enumCls, AnnotationIntrospector ai) {
212+
return _constructFor(enumCls, ai, false);
213+
}
214+
215+
/**
216+
* @deprecated Since 2.12
217+
*/
218+
@Deprecated // since 2.12
219+
public static EnumResolver constructUnsafe(Class<?> rawEnumCls, AnnotationIntrospector ai) {
220+
return _constructFor(rawEnumCls, ai, false);
221+
}
222+
223+
/**
224+
* @deprecated Since 2.12
225+
*/
226+
@Deprecated // since 2.12
227+
public static EnumResolver constructUsingToString(Class<Enum<?>> enumCls,
228+
AnnotationIntrospector ai) {
229+
return _constructUsingToString(enumCls, ai, false);
145230
}
146231

147232
/**
148-
* Method that needs to be used instead of {@link #constructUsingToString}
149-
* if static type of enum is not known.
150-
*
151233
* @since 2.8
234+
* @deprecated Since 2.12
152235
*/
153-
@SuppressWarnings({ "unchecked" })
236+
@Deprecated // since 2.12
154237
public static EnumResolver constructUnsafeUsingToString(Class<?> rawEnumCls,
155-
AnnotationIntrospector ai)
156-
{
157-
// oh so wrong... not much that can be done tho
158-
Class<Enum<?>> enumCls = (Class<Enum<?>>) rawEnumCls;
159-
return constructUsingToString(enumCls, ai);
238+
AnnotationIntrospector ai) {
239+
return _constructUsingToString(rawEnumCls, ai, false);
240+
}
241+
242+
/**
243+
* @deprecated Since 2.8 (remove from 2.13 or later)
244+
*/
245+
@Deprecated
246+
public static EnumResolver constructUsingToString(Class<Enum<?>> enumCls) {
247+
return _constructUsingToString(enumCls, null, false);
248+
}
249+
250+
/**
251+
* @deprecated Since 2.12
252+
*/
253+
@Deprecated
254+
public static EnumResolver constructUsingMethod(Class<Enum<?>> enumCls,
255+
AnnotatedMember accessor, AnnotationIntrospector ai) {
256+
return _constructUsingMethod(enumCls, accessor, ai, false);
160257
}
161258

162259
/**
163-
* Method used when actual String serialization is indicated using @JsonValue
164-
* on a method.
165-
*
166260
* @since 2.9
261+
* @deprecated Since 2.12
167262
*/
168-
@SuppressWarnings({ "unchecked" })
263+
@Deprecated
169264
public static EnumResolver constructUnsafeUsingMethod(Class<?> rawEnumCls,
170-
AnnotatedMember accessor,
171-
AnnotationIntrospector ai)
172-
{
173-
// wrong as ever but:
174-
Class<Enum<?>> enumCls = (Class<Enum<?>>) rawEnumCls;
175-
return constructUsingMethod(enumCls, accessor, ai);
265+
AnnotatedMember accessor, AnnotationIntrospector ai) {
266+
return _constructUsingMethod(rawEnumCls, accessor, ai, false);
176267
}
177268

178-
public CompactStringObjectMap constructLookup() {
179-
return CompactStringObjectMap.construct(_enumsById);
269+
/*
270+
/**********************************************************************
271+
/* Public API
272+
/**********************************************************************
273+
*/
274+
275+
public Enum<?> findEnum(final String key) {
276+
Enum<?> en = _enumsById.get(key);
277+
if (en == null) {
278+
if (_isIgnoreCase) {
279+
return _findEnumCaseInsensitive(key);
280+
}
281+
}
282+
return en;
180283
}
181284

182-
public Enum<?> findEnum(String key) { return _enumsById.get(key); }
285+
// @since 2.12
286+
protected Enum<?> _findEnumCaseInsensitive(final String key) {
287+
for (Map.Entry<String, Enum<?>> entry : _enumsById.entrySet()) {
288+
if (key.equalsIgnoreCase(entry.getKey())) {
289+
return entry.getValue();
290+
}
291+
}
292+
return null;
293+
}
183294

184295
public Enum<?> getEnum(int index) {
185296
if (index < 0 || index >= _enums.length) {

0 commit comments

Comments
 (0)