Skip to content

Commit dd5f6de

Browse files
committed
Fix #2352
1 parent 9c14c46 commit dd5f6de

File tree

8 files changed

+173
-16
lines changed

8 files changed

+173
-16
lines changed

release-notes/CREDITS-2.x

+4
Original file line numberDiff line numberDiff line change
@@ -1015,6 +1015,10 @@ Antonio Petrelli (apetrelli@github)
10151015
* Reported #2049: TreeTraversingParser and UTF8StreamJsonParser create contexts differently
10161016
(2.11.0)
10171017
1018+
Robert Diebels (RobertDiebels@github)
1019+
* Contributed #2352: Support use of `@JsonAlias` for enum values
1020+
(2.11.0)
1021+
10181022
Joseph Koshakow (jkosh44@github)
10191023
* Contributed fix for #2515: `ObjectMapper.registerSubtypes(NamedType...)` doesn't allow registering
10201024
the same POJO for two different type ids

release-notes/VERSION-2.x

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ Project: jackson-databind
88

99
#2049: TreeTraversingParser and UTF8StreamJsonParser create contexts differently
1010
(reported by Antonio P)
11+
#2352: Support use of `@JsonAlias` for enum values
12+
(contributed by Robert D)
1113
#2480: Fix `JavaType.isEnumType()` to support sub-classes
1214
#2487: BeanDeserializerBuilder Protected Factory Method for Extension
1315
(contributed by Ville K)

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

+13
Original file line numberDiff line numberDiff line change
@@ -933,6 +933,19 @@ public String[] findEnumValues(Class<?> enumType, Enum<?>[] enumValues, String[]
933933
return names;
934934
}
935935

936+
/**
937+
* Method that is related to {@link #findEnumValues} but is called to check if
938+
* there are alternative names (aliased) that can be accepted for entries, in
939+
* addition to primary names introspected earlier.
940+
* If so, these aliases should be returned in {@code aliases} {@link List} passed
941+
* as argument (and initialized for proper size by caller).
942+
*
943+
* @since 2.11
944+
*/
945+
public void findEnumAliases(Class<?> enumType, Enum<?>[] enumValues, String[][] aliases) {
946+
;
947+
}
948+
936949
/**
937950
* Finds the Enum value that should be considered the default value, if possible.
938951
*

src/main/java/com/fasterxml/jackson/databind/introspect/AnnotationIntrospectorPair.java

+7
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,13 @@ public String[] findEnumValues(Class<?> enumType, Enum<?>[] enumValues, String[
616616
return names;
617617
}
618618

619+
@Override
620+
public void findEnumAliases(Class<?> enumType, Enum<?>[] enumValues, String[][] aliases) {
621+
// reverse order to give _primary higher precedence
622+
_secondary.findEnumAliases(enumType, enumValues, aliases);
623+
_primary.findEnumAliases(enumType, enumValues, aliases);
624+
}
625+
619626
@Override
620627
public Enum<?> findDefaultEnumValue(Class<Enum<?>> enumCls) {
621628
Enum<?> en = _primary.findDefaultEnumValue(enumCls);

src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java

+24
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,30 @@ public String[] findEnumValues(Class<?> enumType, Enum<?>[] enumValues, String[]
234234
return names;
235235
}
236236

237+
@Override // since 2.11
238+
public void findEnumAliases(Class<?> enumType, Enum<?>[] enumValues, String[][] aliasList)
239+
{
240+
// Main complication: discrepancy between Field that represent enum value,
241+
// Enum abstraction; joint by name but not reference
242+
for (Field f : ClassUtil.getDeclaredFields(enumType)) {
243+
if (f.isEnumConstant()) {
244+
JsonAlias aliasAnnotation = f.getAnnotation(JsonAlias.class);
245+
if (aliasAnnotation != null) {
246+
String[] aliases = aliasAnnotation.value();
247+
if (aliases.length != 0) {
248+
final String name = f.getName();
249+
// Find matching enum (could create Ma
250+
for (int i = 0, end = enumValues.length; i < end; ++i) {
251+
if (name.equals(enumValues[i].name())) {
252+
aliasList[i] = aliases;
253+
}
254+
}
255+
}
256+
}
257+
}
258+
}
259+
}
260+
237261
/**
238262
* Finds the Enum value that should be considered the default value, if possible.
239263
* <p>

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

+36-13
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ public class EnumResolver implements java.io.Serializable
2121

2222
protected final Enum<?> _defaultValue;
2323

24-
protected EnumResolver(Class<Enum<?>> enumClass, Enum<?>[] enums, HashMap<String, Enum<?>> map, Enum<?> defaultValue)
24+
protected EnumResolver(Class<Enum<?>> enumClass, Enum<?>[] enums,
25+
HashMap<String, Enum<?>> map, Enum<?> defaultValue)
2526
{
2627
_enumClass = enumClass;
2728
_enums = enums;
@@ -40,18 +41,28 @@ public static EnumResolver constructFor(Class<Enum<?>> enumCls, AnnotationIntros
4041
throw new IllegalArgumentException("No enum constants for class "+enumCls.getName());
4142
}
4243
String[] names = ai.findEnumValues(enumCls, enumValues, new String[enumValues.length]);
44+
final String[][] allAliases = new String[names.length][];
45+
ai.findEnumAliases(enumCls, enumValues, allAliases);
4346
HashMap<String, Enum<?>> map = new HashMap<String, Enum<?>>();
4447
for (int i = 0, len = enumValues.length; i < len; ++i) {
48+
final Enum<?> enumValue = enumValues[i];
4549
String name = names[i];
4650
if (name == null) {
47-
name = enumValues[i].name();
51+
name = enumValue.name();
52+
}
53+
map.put(name, enumValue);
54+
String[] aliases = allAliases[i];
55+
if (aliases != null) {
56+
for (String alias : aliases) {
57+
// TODO: JDK 1.8, use Map.putIfAbsent()
58+
// Avoid overriding any primary names
59+
if (!map.containsKey(alias)) {
60+
map.put(alias, enumValue);
61+
}
62+
}
4863
}
49-
map.put(name, enumValues[i]);
5064
}
51-
52-
Enum<?> defaultEnum = ai.findDefaultEnumValue(enumCls);
53-
54-
return new EnumResolver(enumCls, enumValues, map, defaultEnum);
65+
return new EnumResolver(enumCls, enumValues, map, ai.findDefaultEnumValue(enumCls));
5566
}
5667

5768
/**
@@ -71,15 +82,27 @@ public static EnumResolver constructUsingToString(Class<Enum<?>> enumCls) {
7182
public static EnumResolver constructUsingToString(Class<Enum<?>> enumCls,
7283
AnnotationIntrospector ai)
7384
{
74-
Enum<?>[] enumValues = enumCls.getEnumConstants();
85+
Enum<?>[] enumConstants = enumCls.getEnumConstants();
7586
HashMap<String, Enum<?>> map = new HashMap<String, Enum<?>>();
87+
final String[][] allAliases = new String[enumConstants.length][];
88+
ai.findEnumAliases(enumCls, enumConstants, allAliases);
89+
7690
// from last to first, so that in case of duplicate values, first wins
77-
for (int i = enumValues.length; --i >= 0; ) {
78-
Enum<?> e = enumValues[i];
79-
map.put(e.toString(), e);
91+
for (int i = enumConstants.length; --i >= 0; ) {
92+
Enum<?> enumValue = enumConstants[i];
93+
map.put(enumValue.toString(), enumValue);
94+
String[] aliases = allAliases[i];
95+
if (aliases != null) {
96+
for (String alias : aliases) {
97+
// TODO: JDK 1.8, use Map.putIfAbsent()
98+
// Avoid overriding any primary names
99+
if (!map.containsKey(alias)) {
100+
map.put(alias, enumValue);
101+
}
102+
}
103+
}
80104
}
81-
Enum<?> defaultEnum = (ai == null) ? null : ai.findDefaultEnumValue(enumCls);
82-
return new EnumResolver(enumCls, enumValues, map, defaultEnum);
105+
return new EnumResolver(enumCls, enumConstants, map, ai.findDefaultEnumValue(enumCls));
83106
}
84107

85108
/**

src/test/java/com/fasterxml/jackson/databind/deser/jdk/EnumAltIdTest.java

+85-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import java.io.IOException;
44
import java.util.EnumSet;
55

6+
import com.fasterxml.jackson.annotation.JsonAlias;
7+
import com.fasterxml.jackson.annotation.JsonEnumDefaultValue;
68
import com.fasterxml.jackson.annotation.JsonFormat;
79
import com.fasterxml.jackson.core.type.TypeReference;
810
import com.fasterxml.jackson.databind.BaseMapTest;
@@ -33,7 +35,39 @@ protected static class StrictCaseBean {
3335
@JsonFormat(without={ JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES })
3436
public TestEnum value;
3537
}
36-
38+
39+
40+
// for [databind#2352]: Support aliases on enum values
41+
enum MyEnum2352_1 {
42+
A,
43+
@JsonAlias({"singleAlias"})
44+
B,
45+
@JsonAlias({"multipleAliases1", "multipleAliases2"})
46+
C
47+
}
48+
// for [databind#2352]: Support aliases on enum values
49+
enum MyEnum2352_2 {
50+
A,
51+
@JsonAlias({"singleAlias"})
52+
B,
53+
@JsonAlias({"multipleAliases1", "multipleAliases2"})
54+
C;
55+
56+
@Override
57+
public String toString() {
58+
return name().toLowerCase();
59+
}
60+
}
61+
// for [databind#2352]: Support aliases on enum values
62+
enum MyEnum2352_3 {
63+
A,
64+
@JsonEnumDefaultValue
65+
@JsonAlias({"singleAlias"})
66+
B,
67+
@JsonAlias({"multipleAliases1", "multipleAliases2"})
68+
C;
69+
}
70+
3771
/*
3872
/**********************************************************
3973
/* Test methods, basic
@@ -59,7 +93,7 @@ public void testFailWhenCaseSensitiveAndNameIsNotUpperCase() throws IOException
5993
verifyException(e, "[JACKSON, OK, RULES]");
6094
}
6195
}
62-
96+
6397
public void testFailWhenCaseSensitiveAndToStringIsUpperCase() throws IOException {
6498
ObjectReader r = READER_DEFAULT.forType(LowerCaseEnum.class)
6599
.with(DeserializationFeature.READ_ENUMS_USING_TO_STRING);
@@ -130,4 +164,53 @@ public void testIgnoreCaseViaFormat() throws Exception
130164
verifyException(e, "[JACKSON, OK, RULES]");
131165
}
132166
}
167+
168+
/*
169+
/**********************************************************
170+
/* Test methods, Enum Aliases [databind#2352]
171+
/**********************************************************
172+
*/
173+
174+
// for [databind#2352]
175+
public void testEnumWithAlias() throws Exception {
176+
ObjectReader reader = MAPPER.readerFor(MyEnum2352_1.class);
177+
MyEnum2352_1 nonAliased = reader.readValue(quote("A"));
178+
assertEquals(MyEnum2352_1.A, nonAliased);
179+
MyEnum2352_1 singleAlias = reader.readValue(quote("singleAlias"));
180+
assertEquals(MyEnum2352_1.B, singleAlias);
181+
MyEnum2352_1 multipleAliases1 = reader.readValue(quote("multipleAliases1"));
182+
assertEquals(MyEnum2352_1.C, multipleAliases1);
183+
MyEnum2352_1 multipleAliases2 = reader.readValue(quote("multipleAliases2"));
184+
assertEquals(MyEnum2352_1.C, multipleAliases2);
185+
}
186+
187+
// for [databind#2352]
188+
public void testEnumWithAliasAndToStringSupported() throws Exception {
189+
ObjectReader reader = MAPPER.readerFor(MyEnum2352_2.class)
190+
.with(DeserializationFeature.READ_ENUMS_USING_TO_STRING);
191+
MyEnum2352_2 nonAliased = reader.readValue(quote("a"));
192+
assertEquals(MyEnum2352_2.A, nonAliased);
193+
MyEnum2352_2 singleAlias = reader.readValue(quote("singleAlias"));
194+
assertEquals(MyEnum2352_2.B, singleAlias);
195+
MyEnum2352_2 multipleAliases1 = reader.readValue(quote("multipleAliases1"));
196+
assertEquals(MyEnum2352_2.C, multipleAliases1);
197+
MyEnum2352_2 multipleAliases2 = reader.readValue(quote("multipleAliases2"));
198+
assertEquals(MyEnum2352_2.C, multipleAliases2);
199+
}
200+
201+
// for [databind#2352]
202+
public void testEnumWithAliasAndDefaultForUnknownValueEnabled() throws Exception {
203+
ObjectReader reader = MAPPER.readerFor(MyEnum2352_3.class)
204+
.with(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE);
205+
MyEnum2352_3 nonAliased = reader.readValue(quote("A"));
206+
assertEquals(MyEnum2352_3.A, nonAliased);
207+
MyEnum2352_3 singleAlias = reader.readValue(quote("singleAlias"));
208+
assertEquals(MyEnum2352_3.B, singleAlias);
209+
MyEnum2352_3 defaulted = reader.readValue(quote("unknownValue"));
210+
assertEquals(MyEnum2352_3.B, defaulted);
211+
MyEnum2352_3 multipleAliases1 = reader.readValue(quote("multipleAliases1"));
212+
assertEquals(MyEnum2352_3.C, multipleAliases1);
213+
MyEnum2352_3 multipleAliases2 = reader.readValue(quote("multipleAliases2"));
214+
assertEquals(MyEnum2352_3.C, multipleAliases2);
215+
}
133216
}

src/test/java/com/fasterxml/jackson/databind/util/EnumValuesTest.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ public void testConstructWithToString() {
5252

5353
public void testEnumResolver()
5454
{
55-
EnumResolver enumRes = EnumResolver.constructUnsafeUsingToString(ABC.class, null);
55+
EnumResolver enumRes = EnumResolver.constructUnsafeUsingToString(ABC.class,
56+
MAPPER.getSerializationConfig().getAnnotationIntrospector());
5657
assertEquals(ABC.B, enumRes.getEnum(1));
5758
assertNull(enumRes.getEnum(-1));
5859
assertNull(enumRes.getEnum(3));

0 commit comments

Comments
 (0)