Skip to content

Commit c7a4b0c

Browse files
committed
Fix #1850 using logic from PR #3242 (with minor modifications)
1 parent c100ed5 commit c7a4b0c

File tree

6 files changed

+157
-49
lines changed

6 files changed

+157
-49
lines changed

release-notes/VERSION-2.x

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ Project: jackson-databind
66

77
2.13.0 (not yet released)
88

9+
#1850: `@JsonValue` with integer for enum does not deserialize correctly
10+
(reported by tgolden-andplus@github)
11+
(fix contributed by limengning@github)
912
#2509: `AnnotatedMethod.getValue()/setValue()` doesn't have useful exception message
1013
(reported by henryptung@github)
1114
(fix contributed by Stephan S)
@@ -60,7 +63,7 @@ Project: jackson-databind
6063
(suggested by Nick B)
6164
#3214: For an absent property Jackson injects `NullNode` instead of `null` to a
6265
JsonNode-typed constructor argument of a `@ConstructorProperties`-annotated constructor
63-
(repored by robvarga@github)
66+
(reported by robvarga@github)
6467
#3217: `XMLGregorianCalendar` doesn't work with default typing
6568
(reported by Xinzhe Y)
6669
#3227: Content `null` handling not working for root values

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

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,14 @@ public class EnumDeserializer
5353

5454
protected final Boolean _caseInsensitive;
5555

56+
/**
57+
* Marker flag for cases where we expect actual integral value for Enum,
58+
* based on {@code @JsonValue} (and equivalent) annotated accessor.
59+
*
60+
* @since 2.13
61+
*/
62+
protected final boolean _isFromIntValue;
63+
5664
/**
5765
* @since 2.9
5866
*/
@@ -63,6 +71,7 @@ public EnumDeserializer(EnumResolver byNameResolver, Boolean caseInsensitive)
6371
_enumsByIndex = byNameResolver.getRawEnums();
6472
_enumDefaultValue = byNameResolver.getDefaultValue();
6573
_caseInsensitive = caseInsensitive;
74+
_isFromIntValue = byNameResolver.isFromIntValue();
6675
}
6776

6877
/**
@@ -75,6 +84,7 @@ protected EnumDeserializer(EnumDeserializer base, Boolean caseInsensitive)
7584
_enumsByIndex = base._enumsByIndex;
7685
_enumDefaultValue = base._enumDefaultValue;
7786
_caseInsensitive = caseInsensitive;
87+
_isFromIntValue = base._isFromIntValue;
7888
}
7989

8090
/**
@@ -84,7 +94,7 @@ protected EnumDeserializer(EnumDeserializer base, Boolean caseInsensitive)
8494
public EnumDeserializer(EnumResolver byNameResolver) {
8595
this(byNameResolver, null);
8696
}
87-
97+
8898
/**
8999
* @deprecated Since 2.8
90100
*/
@@ -190,6 +200,13 @@ public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOEx
190200

191201
// But let's consider int acceptable as well (if within ordinal range)
192202
if (p.hasToken(JsonToken.VALUE_NUMBER_INT)) {
203+
// 26-Sep-2021, tatu: [databind#1850] Special case where we get "true" integer
204+
// enumeration and should avoid use of {@code Enum.index()}
205+
if (_isFromIntValue) {
206+
// ... whether to rely on "getText()" returning String, or get number, convert?
207+
// For now assume all format backends can produce String:
208+
return _fromString(p, ctxt, p.getText());
209+
}
193210
return _fromInteger(p, ctxt, p.getIntValue());
194211
}
195212

@@ -309,7 +326,8 @@ private final Object _deserializeAltString(JsonParser p, DeserializationContext
309326
if (match != null) {
310327
return match;
311328
}
312-
} else if (!ctxt.isEnabled(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)) {
329+
} else if (!ctxt.isEnabled(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)
330+
&& !_isFromIntValue) {
313331
// [databind#149]: Allow use of 'String' indexes as well -- unless prohibited (as per above)
314332
char c = name.charAt(0);
315333
if (c >= '0' && c <= '9') {

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -875,7 +875,9 @@ public static Object defaultValue(Class<?> cls)
875875

876876
/**
877877
* Helper method for finding wrapper type for given primitive type (why isn't
878-
* there one in JDK?)
878+
* there one in JDK?).
879+
* NOTE: throws {@link IllegalArgumentException} if given type is NOT primitive
880+
* type (caller has to check).
879881
*/
880882
public static Class<?> wrapperType(Class<?> primitiveType)
881883
{
@@ -908,7 +910,7 @@ public static Class<?> wrapperType(Class<?> primitiveType)
908910

909911
/**
910912
* Method that can be used to find primitive type for given class if (but only if)
911-
* it is either wrapper type or primitive type; returns `null` if type is neither.
913+
* it is either wrapper type or primitive type; returns {@code null} if type is neither.
912914
*
913915
* @since 2.7
914916
*/

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

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,22 +24,47 @@ public class EnumResolver implements java.io.Serializable
2424
protected final Enum<?> _defaultValue;
2525

2626
/**
27+
* Marker for case-insensitive handling
28+
*
2729
* @since 2.12
2830
*/
2931
protected final boolean _isIgnoreCase;
3032

33+
/**
34+
* Marker for case where value may come from {@code @JsonValue} annotated
35+
* accessor and is expected/likely to come from actual integral number
36+
* value (and not String).
37+
*<p>
38+
* Special case is needed since this specifically means that {@code Enum.index()}
39+
* should NOT be used or default to.
40+
*
41+
* @since 2.13
42+
*/
43+
protected final boolean _isFromIntValue;
44+
3145
/**
3246
* @since 2.12
3347
*/
3448
protected EnumResolver(Class<Enum<?>> enumClass, Enum<?>[] enums,
3549
HashMap<String, Enum<?>> map, Enum<?> defaultValue,
36-
boolean isIgnoreCase)
50+
boolean isIgnoreCase, boolean isFromIntValue)
3751
{
3852
_enumClass = enumClass;
3953
_enums = enums;
4054
_enumsById = map;
4155
_defaultValue = defaultValue;
4256
_isIgnoreCase = isIgnoreCase;
57+
_isFromIntValue = isFromIntValue;
58+
}
59+
60+
/**
61+
* @deprecated Since 2.13
62+
*/
63+
@Deprecated // since 2.13
64+
protected EnumResolver(Class<Enum<?>> enumClass, Enum<?>[] enums,
65+
HashMap<String, Enum<?>> map, Enum<?> defaultValue,
66+
boolean isIgnoreCase) {
67+
this(enumClass, enums, map, defaultValue, isIgnoreCase, false);
4368
}
4469

4570
/**
@@ -85,7 +110,8 @@ protected static EnumResolver _constructFor(Class<?> enumCls0,
85110
}
86111
}
87112
return new EnumResolver(enumCls, enumConstants, map,
88-
_enumDefault(ai, enumCls), isIgnoreCase);
113+
_enumDefault(ai, enumCls), isIgnoreCase,
114+
false);
89115
}
90116

91117
/**
@@ -130,7 +156,7 @@ protected static EnumResolver _constructUsingToString(Class<?> enumCls0,
130156
}
131157
}
132158
return new EnumResolver(enumCls, enumConstants, map,
133-
_enumDefault(ai, enumCls), isIgnoreCase);
159+
_enumDefault(ai, enumCls), isIgnoreCase, false);
134160
}
135161

136162
/**
@@ -167,7 +193,10 @@ protected static EnumResolver _constructUsingMethod(Class<?> enumCls0,
167193
}
168194
}
169195
return new EnumResolver(enumCls, enumConstants, map,
170-
_enumDefault(ai, enumCls), isIgnoreCase);
196+
_enumDefault(ai, enumCls), isIgnoreCase,
197+
// 26-Sep-2021, tatu: [databind#1850] Need to consider "from int" case
198+
_isIntType(accessor.getRawType())
199+
);
171200
}
172201

173202
public CompactStringObjectMap constructLookup() {
@@ -191,6 +220,17 @@ protected static Enum<?> _enumDefault(AnnotationIntrospector intr, Class<?> enum
191220
return (intr != null) ? intr.findDefaultEnumValue(_enumClass(enumCls)) : null;
192221
}
193222

223+
protected static boolean _isIntType(Class<?> erasedType) {
224+
if (erasedType.isPrimitive()) {
225+
erasedType = ClassUtil.wrapperType(erasedType);
226+
}
227+
return (erasedType == Long.class)
228+
|| (erasedType == Integer.class)
229+
|| (erasedType == Short.class)
230+
|| (erasedType == Byte.class)
231+
;
232+
}
233+
194234
/*
195235
/**********************************************************************
196236
/* Deprecated constructors, factory methods
@@ -203,7 +243,7 @@ protected static Enum<?> _enumDefault(AnnotationIntrospector intr, Class<?> enum
203243
@Deprecated // since 2.12
204244
protected EnumResolver(Class<Enum<?>> enumClass, Enum<?>[] enums,
205245
HashMap<String, Enum<?>> map, Enum<?> defaultValue) {
206-
this(enumClass, enums, map, defaultValue, false);
246+
this(enumClass, enums, map, defaultValue, false, false);
207247
}
208248

209249
/**
@@ -327,5 +367,17 @@ public Collection<String> getEnumIds() {
327367
public Class<Enum<?>> getEnumClass() { return _enumClass; }
328368

329369
public int lastValidIndex() { return _enums.length-1; }
370+
371+
/**
372+
* Accessor for checking if we have a special case in which value to map
373+
* is from {@code @JsonValue} annotated accessor with integral type: this
374+
* matters for cases where incoming content value is of integral type
375+
* and should be mapped to specific value and NOT to {@code Enum.index()}.
376+
*
377+
* @since 2.13
378+
*/
379+
public boolean isFromIntValue() {
380+
return _isFromIntValue;
381+
}
330382
}
331383

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package com.fasterxml.jackson.databind.deser.jdk;
2+
3+
import com.fasterxml.jackson.annotation.JsonValue;
4+
5+
import com.fasterxml.jackson.databind.*;
6+
7+
public class EnumDeserFromIntJsonValueTest extends BaseMapTest
8+
{
9+
// [databind#1850]
10+
11+
enum Bean1850IntMethod {
12+
A(101);
13+
final int x;
14+
Bean1850IntMethod(int x) { this.x = x; }
15+
@JsonValue
16+
public int code() { return x; }
17+
}
18+
19+
enum Bean1850IntField {
20+
A(202);
21+
@JsonValue
22+
public final int x;
23+
Bean1850IntField(int x) { this.x = x; }
24+
}
25+
26+
enum Bean1850LongMethod {
27+
A(-13L);
28+
final long x;
29+
Bean1850LongMethod(long x) { this.x = x; }
30+
@JsonValue
31+
public long code() { return x; }
32+
}
33+
34+
enum Bean1850LongField {
35+
A(29L);
36+
@JsonValue
37+
public final long x;
38+
Bean1850LongField(long x) { this.x = x; }
39+
}
40+
41+
private final ObjectMapper MAPPER = newJsonMapper();
42+
43+
// [databind#1850] pass tests
44+
45+
public void testEnumFromInt1850Method() throws Exception
46+
{
47+
String json = MAPPER.writeValueAsString(Bean1850IntMethod.A);
48+
Bean1850IntMethod e1 = MAPPER.readValue(json, Bean1850IntMethod.class);
49+
assertEquals(Bean1850IntMethod.A, e1);
50+
}
51+
52+
public void testEnumFromInt1850Field() throws Exception
53+
{
54+
String json = MAPPER.writeValueAsString(Bean1850IntField.A);
55+
Bean1850IntField e2 = MAPPER.readValue(json, Bean1850IntField.class);
56+
assertEquals(Bean1850IntField.A, e2);
57+
}
58+
59+
public void testEnumFromLong1850Method() throws Exception
60+
{
61+
String json = MAPPER.writeValueAsString(Bean1850LongMethod.A);
62+
Bean1850LongMethod e1 = MAPPER.readValue(json, Bean1850LongMethod.class);
63+
assertEquals(Bean1850LongMethod.A, e1);
64+
}
65+
66+
public void testEnumFromLong1850Field() throws Exception
67+
{
68+
String json = MAPPER.writeValueAsString(Bean1850LongField.A);
69+
Bean1850LongField e2 = MAPPER.readValue(json, Bean1850LongField.class);
70+
assertEquals(Bean1850LongField.A, e2);
71+
}
72+
}

src/test/java/com/fasterxml/jackson/failing/EnumDeserializationFromInt1850Test.java

Lines changed: 0 additions & 39 deletions
This file was deleted.

0 commit comments

Comments
 (0)