Skip to content

Commit 5285e4a

Browse files
committed
Bit more work on #1313 to also support @JsonFormat way of per-property allowance (or not) of case-insensitivity
1 parent 15d4fe2 commit 5285e4a

File tree

8 files changed

+223
-95
lines changed

8 files changed

+223
-95
lines changed

release-notes/CREDITS

+4
Original file line numberDiff line numberDiff line change
@@ -610,3 +610,7 @@ Fabrizio Cucci (fabriziocucci@github)
610610
Emiliano Clariá (emilianogc@github)
611611
* Contributed #1434: Explicitly pass null on invoke calls with no arguments
612612
(2.9.0)
613+
614+
Ana Eliza Barbosa (AnaEliza@github)
615+
* Contributed #1520: Case insensitive enum deserialization feature.
616+
(2.9.0)

release-notes/VERSION

+2
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ Project: jackson-databind
5252
(suggested by PawelJagus@github)
5353
#1454: Support `@JsonFormat.lenient` for `java.util.Date`, `java.util.Calendar`
5454
#1474: Replace use of `Class.newInstance()` (deprecated in Java 9) with call via Constructor
55+
#1520: Case insensitive enum deserialization feature.
56+
(contributed by Ana-Eliza B)
5557

5658
2.8.8 (not yet released)
5759

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

+26-22
Original file line numberDiff line numberDiff line change
@@ -92,28 +92,7 @@ public enum DeserializationFeature implements ConfigFeature
9292
* {@link java.util.List}s.
9393
*/
9494
USE_JAVA_ARRAY_FOR_JSON_ARRAY(false),
95-
96-
/**
97-
* Feature that determines standard deserialization mechanism used for
98-
* Enum values: if enabled, Enums are assumed to have been serialized using
99-
* return value of <code>Enum.toString()</code>;
100-
* if disabled, return value of <code>Enum.name()</code> is assumed to have been used.
101-
*<p>
102-
* Note: this feature should usually have same value
103-
* as {@link SerializationFeature#WRITE_ENUMS_USING_TO_STRING}.
104-
*<p>
105-
* Feature is disabled by default.
106-
*/
107-
READ_ENUMS_USING_TO_STRING(false),
108-
109-
/**
110-
* Feature that determines if Enum deserialization should be case sensitive or not.
111-
* If enabled, Enum deserialization will ignore case.
112-
* <p>
113-
* Feature is disabled by default.
114-
*/
115-
READ_ENUMS_IGNORING_CASE(false),
116-
95+
11796
/*
11897
/******************************************************
11998
* Error handling features
@@ -373,6 +352,31 @@ public enum DeserializationFeature implements ConfigFeature
373352
*/
374353
ACCEPT_FLOAT_AS_INT(true),
375354

355+
/**
356+
* Feature that determines standard deserialization mechanism used for
357+
* Enum values: if enabled, Enums are assumed to have been serialized using
358+
* return value of <code>Enum.toString()</code>;
359+
* if disabled, return value of <code>Enum.name()</code> is assumed to have been used.
360+
*<p>
361+
* Note: this feature should usually have same value
362+
* as {@link SerializationFeature#WRITE_ENUMS_USING_TO_STRING}.
363+
*<p>
364+
* Feature is disabled by default.
365+
*/
366+
READ_ENUMS_USING_TO_STRING(false),
367+
368+
/**
369+
* Feature that determines if Enum deserialization should be case sensitive or not.
370+
* If enabled, Enum deserialization will ignore case, that is, case of incoming String
371+
* value and enum id (dependant on other settings, either `name()`, `toString()`, or
372+
* explicit override) do not need to match.
373+
* <p>
374+
* Feature is disabled by default.
375+
*
376+
* @since 2.9
377+
*/
378+
READ_ENUMS_IGNORING_CASE(false),
379+
376380
/**
377381
* Feature that allows unknown Enum values to be parsed as null values.
378382
* If disabled, unknown Enum values will throw exceptions.

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

+59-16
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@
22

33
import java.io.IOException;
44

5+
import com.fasterxml.jackson.annotation.JsonFormat;
6+
57
import com.fasterxml.jackson.core.*;
8+
69
import com.fasterxml.jackson.databind.*;
710
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
11+
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
812
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
913
import com.fasterxml.jackson.databind.deser.ValueInstantiator;
1014
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
@@ -19,6 +23,7 @@
1923
@JacksonStdImpl // was missing until 2.6
2024
public class EnumDeserializer
2125
extends StdScalarDeserializer<Object>
26+
implements ContextualDeserializer
2227
{
2328
private static final long serialVersionUID = 1L;
2429

@@ -41,13 +46,28 @@ public class EnumDeserializer
4146
* @since 2.7.3
4247
*/
4348
protected CompactStringObjectMap _lookupByToString;
44-
49+
50+
protected final Boolean _caseInsensitive;
51+
4552
public EnumDeserializer(EnumResolver byNameResolver)
4653
{
4754
super(byNameResolver.getEnumClass());
4855
_lookupByName = byNameResolver.constructLookup();
4956
_enumsByIndex = byNameResolver.getRawEnums();
5057
_enumDefaultValue = byNameResolver.getDefaultValue();
58+
_caseInsensitive = false;
59+
}
60+
61+
/**
62+
* @since 2.9
63+
*/
64+
protected EnumDeserializer(EnumDeserializer base, Boolean caseInsensitive)
65+
{
66+
super(base);
67+
_lookupByName = base._lookupByName;
68+
_enumsByIndex = base._enumsByIndex;
69+
_enumDefaultValue = base._enumDefaultValue;
70+
_caseInsensitive = caseInsensitive;
5171
}
5272

5373
/**
@@ -98,6 +118,25 @@ public static JsonDeserializer<?> deserializerForNoArgsCreator(DeserializationCo
98118
return new FactoryBasedEnumDeserializer(enumClass, factory);
99119
}
100120

121+
/**
122+
* @since 2.9
123+
*/
124+
public EnumDeserializer withResolved(Boolean caseInsensitive) {
125+
if (_caseInsensitive == caseInsensitive) {
126+
return this;
127+
}
128+
return new EnumDeserializer(this, caseInsensitive);
129+
}
130+
131+
@Override // since 2.9
132+
public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
133+
BeanProperty property) throws JsonMappingException
134+
{
135+
Boolean caseInsensitive = findFormatFeature(ctxt, property, handledType(),
136+
JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES);
137+
return withResolved(caseInsensitive);
138+
}
139+
101140
/*
102141
/**********************************************************
103142
/* Default JsonDeserializer implementation
@@ -167,24 +206,28 @@ private final Object _deserializeAltString(JsonParser p, DeserializationContext
167206
if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)) {
168207
return null;
169208
}
170-
} else if (ctxt.isEnabled(DeserializationFeature.READ_ENUMS_IGNORING_CASE)) {
209+
} else {
171210
// [databind#1313]: Case insensitive enum deserialization
172-
for (String key : lookup.keys()) {
173-
if (key.equalsIgnoreCase(name)) {
174-
return lookup.find(key);
211+
if ((_caseInsensitive == Boolean.TRUE) ||
212+
((_caseInsensitive == null) &&
213+
ctxt.isEnabled(DeserializationFeature.READ_ENUMS_IGNORING_CASE))) {
214+
for (String key : lookup.keys()) {
215+
if (key.equalsIgnoreCase(name)) {
216+
return lookup.find(key);
217+
}
175218
}
176-
}
177-
} else if (!ctxt.isEnabled(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)) {
178-
// [databind#149]: Allow use of 'String' indexes as well -- unless prohibited (as per above)
179-
char c = name.charAt(0);
180-
if (c >= '0' && c <= '9') {
181-
try {
182-
int index = Integer.parseInt(name);
183-
if (index >= 0 && index < _enumsByIndex.length) {
184-
return _enumsByIndex[index];
219+
} else if (!ctxt.isEnabled(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)) {
220+
// [databind#149]: Allow use of 'String' indexes as well -- unless prohibited (as per above)
221+
char c = name.charAt(0);
222+
if (c >= '0' && c <= '9') {
223+
try {
224+
int index = Integer.parseInt(name);
225+
if (index >= 0 && index < _enumsByIndex.length) {
226+
return _enumsByIndex[index];
227+
}
228+
} catch (NumberFormatException e) {
229+
// fine, ignore, was not an integer
185230
}
186-
} catch (NumberFormatException e) {
187-
// fine, ignore, was not an integer
188231
}
189232
}
190233
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public EnumSetDeserializer(JavaType enumType, JsonDeserializer<?> deser)
6363
@SuppressWarnings("unchecked" )
6464
protected EnumSetDeserializer(EnumSetDeserializer base,
6565
JsonDeserializer<?> deser, Boolean unwrapSingle) {
66-
super(EnumSet.class);
66+
super(base);
6767
_enumType = base._enumType;
6868
_enumClass = base._enumClass;
6969
_enumDeserializer = (JsonDeserializer<Enum<?>>) deser;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package com.fasterxml.jackson.databind.deser.jdk;
2+
3+
import java.io.IOException;
4+
import java.util.EnumSet;
5+
6+
import com.fasterxml.jackson.annotation.JsonFormat;
7+
import com.fasterxml.jackson.core.type.TypeReference;
8+
import com.fasterxml.jackson.databind.BaseMapTest;
9+
import com.fasterxml.jackson.databind.DeserializationFeature;
10+
import com.fasterxml.jackson.databind.ObjectMapper;
11+
import com.fasterxml.jackson.databind.ObjectReader;
12+
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
13+
14+
public class EnumAltIdTest extends BaseMapTest
15+
{
16+
// [databind#1313]
17+
18+
enum TestEnum { JACKSON, RULES, OK; }
19+
protected enum LowerCaseEnum {
20+
A, B, C;
21+
private LowerCaseEnum() { }
22+
@Override
23+
public String toString() { return name().toLowerCase(); }
24+
}
25+
26+
protected static class EnumBean {
27+
@JsonFormat(with={ JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES })
28+
public TestEnum value;
29+
}
30+
31+
protected static class StrictCaseBean {
32+
@JsonFormat(without={ JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES })
33+
public TestEnum value;
34+
}
35+
36+
/*
37+
/**********************************************************
38+
/* Test methods, basic
39+
/**********************************************************
40+
*/
41+
42+
protected final ObjectMapper MAPPER = new ObjectMapper();
43+
44+
protected final ObjectReader READER_DEFAULT = MAPPER.reader();
45+
protected final ObjectReader READER_IGNORE_CASE = MAPPER
46+
.reader(DeserializationFeature.READ_ENUMS_IGNORING_CASE);
47+
48+
// Tests for [databind#1313], case-insensitive
49+
50+
public void testFailWhenCaseSensitiveAndNameIsNotUpperCase() throws IOException {
51+
try {
52+
READER_DEFAULT.forType(TestEnum.class).readValue("\"Jackson\"");
53+
fail("InvalidFormatException expected");
54+
} catch (InvalidFormatException e) {
55+
verifyException(e, "value not one of declared Enum instance names: [JACKSON, OK, RULES]");
56+
}
57+
}
58+
59+
public void testFailWhenCaseSensitiveAndToStringIsUpperCase() throws IOException {
60+
ObjectReader r = READER_DEFAULT.forType(LowerCaseEnum.class)
61+
.with(DeserializationFeature.READ_ENUMS_USING_TO_STRING);
62+
try {
63+
r.readValue("\"A\"");
64+
fail("InvalidFormatException expected");
65+
} catch (InvalidFormatException e) {
66+
verifyException(e, "value not one of declared Enum instance names: [a, b, c]");
67+
}
68+
}
69+
70+
public void testEnumDesIgnoringCaseWithLowerCaseContent() throws IOException {
71+
assertEquals(TestEnum.JACKSON,
72+
READER_IGNORE_CASE.forType(TestEnum.class).readValue(quote("jackson")));
73+
}
74+
75+
public void testEnumDesIgnoringCaseWithUpperCaseToString() throws IOException {
76+
ObjectReader r = MAPPER.readerFor(LowerCaseEnum.class)
77+
.with(DeserializationFeature.READ_ENUMS_USING_TO_STRING,
78+
DeserializationFeature.READ_ENUMS_IGNORING_CASE);
79+
assertEquals(LowerCaseEnum.A, r.readValue("\"A\""));
80+
}
81+
82+
/*
83+
/**********************************************************
84+
/* Test methods, containers
85+
/**********************************************************
86+
*/
87+
88+
public void testIgnoreCaseInEnumList() throws Exception {
89+
TestEnum[] enums = READER_IGNORE_CASE.forType(TestEnum[].class)
90+
.readValue("[\"jackson\", \"rules\"]");
91+
92+
assertEquals(2, enums.length);
93+
assertEquals(TestEnum.JACKSON, enums[0]);
94+
assertEquals(TestEnum.RULES, enums[1]);
95+
}
96+
97+
public void testIgnoreCaseInEnumSet() throws IOException {
98+
ObjectReader r = READER_IGNORE_CASE.forType(new TypeReference<EnumSet<TestEnum>>() { });
99+
EnumSet<TestEnum> set = r.readValue("[\"jackson\"]");
100+
assertEquals(1, set.size());
101+
assertTrue(set.contains(TestEnum.JACKSON));
102+
}
103+
104+
/*
105+
/**********************************************************
106+
/* Test methods, property overrides
107+
/**********************************************************
108+
*/
109+
110+
public void testIgnoreCaseViaFormat() throws Exception
111+
{
112+
final String JSON = aposToQuotes("{'value':'ok'}");
113+
114+
// should be able to allow on per-case basis:
115+
EnumBean pojo = READER_DEFAULT.forType(EnumBean.class)
116+
.readValue(JSON);
117+
assertEquals(TestEnum.OK, pojo.value);
118+
119+
// including disabling acceptance
120+
try {
121+
READER_DEFAULT.forType(StrictCaseBean.class)
122+
.readValue(JSON);
123+
fail("Should not pass");
124+
} catch (InvalidFormatException e) {
125+
verifyException(e, "value not one of declared Enum instance names: [JACKSON, OK, RULES]");
126+
}
127+
}
128+
}

src/test/java/com/fasterxml/jackson/databind/deser/EnumDefaultReadTest.java renamed to src/test/java/com/fasterxml/jackson/databind/deser/jdk/EnumDefaultReadTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.fasterxml.jackson.databind.deser;
1+
package com.fasterxml.jackson.databind.deser.jdk;
22

33
import java.io.IOException;
44

0 commit comments

Comments
 (0)