Skip to content

Commit d4220ed

Browse files
authored
Add test, fix #5271 (#5282)
1 parent eee6226 commit d4220ed

File tree

7 files changed

+116
-26
lines changed

7 files changed

+116
-26
lines changed

release-notes/CREDITS-2.x

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1969,3 +1969,8 @@ Plamen Tanov (@ptanov)
19691969
* Reported #2678: `@JacksonInject` added to property overrides value from the JSON
19701970
even if `useInput` is `OptBoolean.TRUE`
19711971
(2.20.0)
1972+
1973+
Michael Reiche (@mikereiche)
1974+
* Reported #5271: `EnumDeserializer` fails to deserialize Enums with @JsonValue - uses table
1975+
with name() key instead of @JsonValue key
1976+
(2.20.0)

release-notes/VERSION-2.x

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ Not yet released
2323
#5242: Support "binary vectors": `@JsonFormat(shape = Shape.BINARY)` for
2424
`float[]`, `double[]`
2525
#5257: Deprecate `URL`-taking `readValue()` methods in `ObjectMapper`, `ObjectReader`
26+
#5271: `EnumDeserializer` fails to deserialize Enums with @JsonValue - uses table
27+
with name() key instead of @JsonValue key
28+
(reported by Michael R)
2629

2730
2.20.0-rc1 (04-Aug-2025)
2831

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

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,14 @@ public class EnumDeserializer
6767
*/
6868
protected final boolean _isFromIntValue;
6969

70+
/**
71+
* Marker flag that indicates whether the Enum class has {@code @JsonValue}
72+
* annotated accessor (or equivalent), used to populate {@link #_lookupByName}.
73+
*
74+
* @since 2.20
75+
*/
76+
protected final boolean _hasAsValueAnnotation;
77+
7078
/**
7179
* Look up map with <b>key</b> as <code>Enum.name()</code> converted by
7280
* {@link EnumNamingStrategy#convertEnumToExternalName(String)}
@@ -96,6 +104,7 @@ public EnumDeserializer(EnumResolver byNameResolver, boolean caseInsensitive,
96104
{
97105
super(byNameResolver.getEnumClass());
98106
_lookupByName = byNameResolver.constructLookup();
107+
_hasAsValueAnnotation = byNameResolver.hasAsValueAnnotation();
99108
_enumsByIndex = byNameResolver.getRawEnums();
100109
_enumDefaultValue = byNameResolver.getDefaultValue();
101110
_caseInsensitive = caseInsensitive;
@@ -112,6 +121,7 @@ public EnumDeserializer(EnumResolver byNameResolver, boolean caseInsensitive,
112121
{
113122
super(byNameResolver.getEnumClass());
114123
_lookupByName = byNameResolver.constructLookup();
124+
_hasAsValueAnnotation = byNameResolver.hasAsValueAnnotation();
115125
_enumsByIndex = byNameResolver.getRawEnums();
116126
_enumDefaultValue = byNameResolver.getDefaultValue();
117127
_caseInsensitive = caseInsensitive;
@@ -128,6 +138,7 @@ protected EnumDeserializer(EnumDeserializer base, Boolean caseInsensitive,
128138
{
129139
super(base);
130140
_lookupByName = base._lookupByName;
141+
_hasAsValueAnnotation = base._hasAsValueAnnotation;
131142
_enumsByIndex = base._enumsByIndex;
132143
_enumDefaultValue = base._enumDefaultValue;
133144
_caseInsensitive = Boolean.TRUE.equals(caseInsensitive);
@@ -138,15 +149,6 @@ protected EnumDeserializer(EnumDeserializer base, Boolean caseInsensitive,
138149
_lookupByToString = base._lookupByToString;
139150
}
140151

141-
/**
142-
* @since 2.9
143-
* @deprecated Since 2.15
144-
*/
145-
@Deprecated
146-
protected EnumDeserializer(EnumDeserializer base, Boolean caseInsensitive) {
147-
this(base, caseInsensitive, null, null);
148-
}
149-
150152
/**
151153
* Factory method used when Enum instances are to be deserialized
152154
* using a creator (static factory method)
@@ -318,9 +320,10 @@ private CompactStringObjectMap _resolveCurrentLookup(DeserializationContext ctxt
318320
if (_lookupByEnumNaming != null) {
319321
return _lookupByEnumNaming;
320322
}
321-
return ctxt.isEnabled(DeserializationFeature.READ_ENUMS_USING_TO_STRING)
322-
? _getToStringLookup(ctxt)
323-
: _lookupByName;
323+
if (_hasAsValueAnnotation || !ctxt.isEnabled(DeserializationFeature.READ_ENUMS_USING_TO_STRING)) {
324+
return _lookupByName;
325+
}
326+
return _getToStringLookup(ctxt);
324327
}
325328

326329
protected Object _fromInteger(JsonParser p, DeserializationContext ctxt,

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -450,10 +450,11 @@ protected EnumResolver _resolveCurrentResolver(DeserializationContext ctxt) {
450450
if (_byEnumNamingResolver != null) {
451451
return _byEnumNamingResolver;
452452
}
453-
if (ctxt.isEnabled(DeserializationFeature.READ_ENUMS_USING_TO_STRING)) {
454-
return _getToStringResolver(ctxt);
453+
if (_byNameResolver.hasAsValueAnnotation()
454+
|| !ctxt.isEnabled(DeserializationFeature.READ_ENUMS_USING_TO_STRING)) {
455+
return _byNameResolver;
455456
}
456-
return _byNameResolver;
457+
return _getToStringResolver(ctxt);
457458
}
458459

459460
/**

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

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,25 +44,47 @@ public class EnumResolver implements java.io.Serializable
4444
*/
4545
protected final boolean _isFromIntValue;
4646

47+
/**
48+
* Marker for case where enum values to match are from {@code @JsonValue}-annotated
49+
* method.
50+
*
51+
* @since 2.20
52+
*/
53+
protected final boolean _hasAsValueAnnotation;
54+
4755
/*
4856
/**********************************************************************
4957
/* Constructors
5058
/**********************************************************************
5159
*/
5260

5361
/**
54-
* @since 2.12
62+
* @since 2.20
5563
*/
5664
protected EnumResolver(Class<Enum<?>> enumClass, Enum<?>[] enums,
5765
HashMap<String, Enum<?>> map, Enum<?> defaultValue,
58-
boolean isIgnoreCase, boolean isFromIntValue)
66+
boolean isIgnoreCase, boolean isFromIntValue,
67+
boolean hasAsValueAnnotation)
5968
{
6069
_enumClass = enumClass;
6170
_enums = enums;
6271
_enumsById = map;
6372
_defaultValue = defaultValue;
6473
_isIgnoreCase = isIgnoreCase;
6574
_isFromIntValue = isFromIntValue;
75+
_hasAsValueAnnotation = hasAsValueAnnotation;
76+
}
77+
78+
/**
79+
* @since 2.12
80+
* @deprecated Since 2.20; use variant that also takes {@code hasAsValueAnnotation} argument
81+
*/
82+
@Deprecated
83+
protected EnumResolver(Class<Enum<?>> enumClass, Enum<?>[] enums,
84+
HashMap<String, Enum<?>> map, Enum<?> defaultValue,
85+
boolean isIgnoreCase, boolean isFromIntValue)
86+
{
87+
this(enumClass, enums, map, defaultValue, isIgnoreCase, isFromIntValue, false);
6688
}
6789

6890
/*
@@ -98,7 +120,7 @@ public static EnumResolver constructFor(DeserializationConfig config,
98120
ai.findEnumAliases(config, annotatedClass, enumConstants, allAliases);
99121

100122
// finally, build
101-
HashMap<String, Enum<?>> map = new HashMap<String, Enum<?>>();
123+
HashMap<String, Enum<?>> map = new HashMap<>();
102124
for (int i = 0, len = enumConstants.length; i < len; ++i) {
103125
final Enum<?> enumValue = enumConstants[i];
104126
String name = names[i];
@@ -116,7 +138,7 @@ public static EnumResolver constructFor(DeserializationConfig config,
116138
}
117139
return new EnumResolver(enumCls, enumConstants, map,
118140
_enumDefault(ai, annotatedClass, enumConstants),
119-
isIgnoreCase, false);
141+
isIgnoreCase, false, false);
120142
}
121143

122144
/**
@@ -163,7 +185,7 @@ public static EnumResolver constructUsingToString(DeserializationConfig config,
163185
}
164186
return new EnumResolver(enumCls, enumConstants, map,
165187
_enumDefault(ai, annotatedClass, enumConstants),
166-
isIgnoreCase, false);
188+
isIgnoreCase, false, false);
167189
}
168190

169191
/**
@@ -191,7 +213,7 @@ public static EnumResolver constructUsingIndex(DeserializationConfig config,
191213
}
192214
return new EnumResolver(enumCls, enumConstants, map,
193215
_enumDefault(ai, annotatedClass, enumConstants),
194-
isIgnoreCase, false);
216+
isIgnoreCase, false, false);
195217
}
196218

197219
/**
@@ -238,7 +260,7 @@ public static EnumResolver constructUsingEnumNamingStrategy(DeserializationConfi
238260

239261
return new EnumResolver(enumCls, enumConstants, map,
240262
_enumDefault(ai, annotatedClass, enumConstants),
241-
isIgnoreCase, false);
263+
isIgnoreCase, false, false);
242264
}
243265

244266
/**
@@ -275,7 +297,8 @@ public static EnumResolver constructUsingMethod(DeserializationConfig config,
275297
_enumDefault(ai, annotatedClass, enumConstants),
276298
isIgnoreCase,
277299
// 26-Sep-2021, tatu: [databind#1850] Need to consider "from int" case
278-
_isIntType(accessor.getRawType())
300+
_isIntType(accessor.getRawType()),
301+
true
279302
);
280303
}
281304

@@ -388,5 +411,15 @@ public Collection<String> getEnumIds() {
388411
public boolean isFromIntValue() {
389412
return _isFromIntValue;
390413
}
414+
415+
/**
416+
* Accessor for checking whether {@code @JsonValue} annotated accessor is used
417+
* to get enum values to use for deserialization.
418+
*
419+
* @since 2.20
420+
*/
421+
public boolean hasAsValueAnnotation() {
422+
return _hasAsValueAnnotation;
423+
}
391424
}
392425

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.fasterxml.jackson.databind.deser.enums;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import com.fasterxml.jackson.annotation.JsonValue;
6+
7+
import com.fasterxml.jackson.databind.*;
8+
import com.fasterxml.jackson.databind.testutil.DatabindTestUtil;
9+
10+
import static org.junit.jupiter.api.Assertions.assertEquals;
11+
12+
public class EnumDeserializerJsonValue5271Test extends DatabindTestUtil
13+
{
14+
enum Enum5271 {
15+
T10("10%"), T20("20%"), T30("30%");
16+
17+
private final String code;
18+
19+
Enum5271(String code) {
20+
this.code = code;
21+
}
22+
23+
@JsonValue
24+
public String getCode() {
25+
return code;
26+
}
27+
}
28+
29+
private final ObjectReader ENUM_READER = newJsonMapper().readerFor(Enum5271.class);
30+
31+
// [databind#5271]
32+
@Test
33+
void convertStringToEnum() throws Exception {
34+
_testConvert(ENUM_READER.without(DeserializationFeature.READ_ENUMS_USING_TO_STRING));
35+
_testConvert(ENUM_READER.with(DeserializationFeature.READ_ENUMS_USING_TO_STRING));
36+
}
37+
38+
private void _testConvert(ObjectReader reader) throws Exception {
39+
assertEquals(Enum5271.T20, reader.readValue(q("20%")));
40+
}
41+
}

src/test/java/com/fasterxml/jackson/databind/ser/EnumAsMapKeyTest.java renamed to src/test/java/com/fasterxml/jackson/databind/ser/enums/EnumAsMapKeyTest.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.fasterxml.jackson.databind.ser;
1+
package com.fasterxml.jackson.databind.ser.enums;
22

33
import java.io.IOException;
44
import java.util.*;
@@ -142,8 +142,12 @@ public void testJsonValueForEnumMapKeySer() throws Exception {
142142

143143
@Test
144144
public void testJsonValueForEnumMapKeyDeser() throws Exception {
145-
MyStuff594 result = MAPPER.readValue(a2q("{'stuff':{'longValue':'foo'}}"),
146-
MyStuff594.class);
145+
final String json = a2q("{'stuff':{'longValue':'foo'}}");
146+
ObjectReader r = MAPPER.readerFor(MyStuff594.class);
147+
MyStuff594 result = r.with(DeserializationFeature.READ_ENUMS_USING_TO_STRING).readValue(json);
148+
assertEquals("foo", result.stuff.get(MyEnum594.VALUE_WITH_A_REALLY_LONG_NAME_HERE));
149+
150+
result = r.without(DeserializationFeature.READ_ENUMS_USING_TO_STRING).readValue(json);
147151
assertEquals("foo", result.stuff.get(MyEnum594.VALUE_WITH_A_REALLY_LONG_NAME_HERE));
148152
}
149153

0 commit comments

Comments
 (0)