Skip to content

Commit 85f4b55

Browse files
committed
Merge branch '2.18' into 2.19
2 parents fdf77f9 + 7835917 commit 85f4b55

File tree

7 files changed

+181
-13
lines changed

7 files changed

+181
-13
lines changed

release-notes/VERSION-2.x

+5
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ Project: jackson-databind
2525
(requested by @nathanukey)
2626
(fix by Joo-Hyuk K)
2727

28+
2.18.2 (not yet released)
29+
30+
#4733: Wrong serialization of Type Ids for certain types of Enum values
31+
(reported by @nlisker)
32+
2833
2.18.1 (28-Oct-2024)
2934

3035
#4741: When `Include.NON_DEFAULT` setting is used on POJO, empty values

src/main/java/com/fasterxml/jackson/databind/jsontype/impl/ClassNameIdResolver.java

+1-11
Original file line numberDiff line numberDiff line change
@@ -92,17 +92,7 @@ protected JavaType _typeFromId(String id, DatabindContext ctxt) throws IOExcepti
9292

9393
protected String _idFrom(Object value, Class<?> cls, TypeFactory typeFactory)
9494
{
95-
// Need to ensure that "enum subtypes" work too
96-
if (ClassUtil.isEnumType(cls)) {
97-
// 29-Sep-2019, tatu: `Class.isEnum()` only returns true for main declaration,
98-
// but NOT from sub-class thereof (extending individual values). This
99-
// is why additional resolution is needed: we want class that contains
100-
// enumeration instances.
101-
if (!cls.isEnum()) {
102-
// and this parent would then have `Enum.class` as its parent:
103-
cls = cls.getSuperclass();
104-
}
105-
}
95+
cls = _resolveToParentAsNecessary(cls);
10696
String str = cls.getName();
10797
if (str.startsWith(JAVA_UTIL_PKG)) {
10898
// 25-Jan-2009, tatu: There are some internal classes that we cannot access as is.

src/main/java/com/fasterxml/jackson/databind/jsontype/impl/MinimalClassNameIdResolver.java

+11-2
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,23 @@ public static MinimalClassNameIdResolver construct(JavaType baseType, MapperConf
5858
@Override
5959
public String idFromValue(Object value)
6060
{
61-
String n = value.getClass().getName();
61+
return idFromValueAndType(value, value.getClass());
62+
}
63+
64+
@Override
65+
public String idFromValueAndType(Object value, Class<?> rawType) {
66+
// 04-Nov-2024, tatu: [databind#4733] Need to resolve enum sub-classes
67+
// same way "ClassNameIdResolver" does
68+
rawType = _resolveToParentAsNecessary(rawType);
69+
String n = rawType.getName();
6270
if (n.startsWith(_basePackagePrefix)) {
6371
// note: we will leave the leading dot in there
6472
return n.substring(_basePackagePrefix.length()-1);
6573
}
6674
return n;
75+
6776
}
68-
77+
6978
@Override
7079
protected JavaType _typeFromId(String id, DatabindContext ctxt) throws IOException
7180
{

src/main/java/com/fasterxml/jackson/databind/jsontype/impl/SimpleNameIdResolver.java

+4
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,10 @@ protected String idFromClass(Class<?> clazz)
120120
if (clazz == null) {
121121
return null;
122122
}
123+
// 04-Nov-2024, tatu: [databind#4733] Need to resolve enum sub-classes
124+
// same way "ClassNameIdResolver" does
125+
clazz = _resolveToParentAsNecessary(clazz);
126+
123127
// NOTE: although we may need to let `TypeModifier` change actual type to use
124128
// for id, we can use original type as key for more efficient lookup:
125129
final String key = clazz.getName();

src/main/java/com/fasterxml/jackson/databind/jsontype/impl/TypeIdResolverBase.java

+29
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.fasterxml.jackson.databind.JavaType;
77
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
88
import com.fasterxml.jackson.databind.type.TypeFactory;
9+
import com.fasterxml.jackson.databind.util.ClassUtil;
910

1011
/**
1112
* Partial base implementation of {@link TypeIdResolver}: all custom implementations
@@ -69,4 +70,32 @@ public JavaType typeFromId(DatabindContext context, String id) throws IOExcepti
6970
public String getDescForKnownTypeIds() {
7071
return null;
7172
}
73+
74+
/**
75+
* Helper method for ensuring we properly resolve cases where we don't
76+
* want to use given instance class due to it being a specific inner class
77+
* but rather enclosing (or parent) class. Specific case we know of
78+
* currently are "enum subtypes", cases
79+
* where simple Enum constant has overrides and uses generated sub-class
80+
* if parent Enum type. In this case we need to ensure that we use
81+
* the main/parent Enum type, not sub-class.
82+
*
83+
* @param cls Class to check and possibly resolve
84+
* @return Resolved class to use
85+
* @since 2.18.2
86+
*/
87+
protected Class<?> _resolveToParentAsNecessary(Class<?> cls) {
88+
// Need to ensure that "enum subtypes" work too
89+
if (ClassUtil.isEnumType(cls)) {
90+
// 29-Sep-2019, tatu: `Class.isEnum()` only returns true for main declaration,
91+
// but NOT from sub-class thereof (extending individual values). This
92+
// is why additional resolution is needed: we want class that contains
93+
// enumeration instances.
94+
if (!cls.isEnum()) {
95+
// and this parent would then have `Enum.class` as its parent:
96+
cls = cls.getSuperclass();
97+
}
98+
}
99+
return cls;
100+
}
72101
}

src/main/java/com/fasterxml/jackson/databind/jsontype/impl/TypeNameIdResolver.java

+4
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ protected String idFromClass(Class<?> clazz)
121121
if (clazz == null) {
122122
return null;
123123
}
124+
// 04-Nov-2024, tatu: [databind#4733] Need to resolve enum sub-classes
125+
// same way "ClassNameIdResolver" does
126+
clazz = _resolveToParentAsNecessary(clazz);
127+
124128
// NOTE: although we may need to let `TypeModifier` change actual type to use
125129
// for id, we can use original type as key for more efficient lookup:
126130
final String key = clazz.getName();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
package com.fasterxml.jackson.databind.jsontype.jdk;
2+
3+
import com.fasterxml.jackson.annotation.*;
4+
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
5+
6+
import com.fasterxml.jackson.databind.*;
7+
import com.fasterxml.jackson.databind.testutil.DatabindTestUtil;
8+
9+
import static org.junit.jupiter.api.Assertions.assertEquals;
10+
11+
import org.junit.jupiter.api.Test;
12+
13+
public class EnumTyping4733Test extends DatabindTestUtil
14+
{
15+
// Baseline case that already worked
16+
@JsonTypeInfo(use = Id.CLASS)
17+
@JsonSubTypes({
18+
@JsonSubTypes.Type(value = A_CLASS.class),
19+
})
20+
interface InterClass {
21+
default void yes() {}
22+
}
23+
24+
enum A_CLASS implements InterClass {
25+
A1,
26+
A2 {
27+
@Override
28+
public void yes() { }
29+
};
30+
}
31+
32+
// Failed before fix for [databind#4733]
33+
@JsonTypeInfo(use = Id.MINIMAL_CLASS)
34+
@JsonSubTypes({
35+
@JsonSubTypes.Type(value = A_MIN_CLASS.class),
36+
})
37+
interface InterMinimalClass {
38+
default void yes() {}
39+
}
40+
41+
enum A_MIN_CLASS implements InterMinimalClass {
42+
A1,
43+
A2 {
44+
@Override
45+
public void yes() { }
46+
};
47+
}
48+
49+
// Failed before fix for [databind#4733]
50+
@JsonTypeInfo(use = Id.NAME)
51+
@JsonSubTypes({
52+
@JsonSubTypes.Type(value = A_NAME.class),
53+
})
54+
interface InterName {
55+
default void yes() {}
56+
}
57+
58+
enum A_NAME implements InterName {
59+
A1,
60+
A2 {
61+
@Override
62+
public void yes() { }
63+
};
64+
}
65+
66+
// Failed before fix for [databind#4733]
67+
@JsonTypeInfo(use = Id.SIMPLE_NAME)
68+
@JsonSubTypes({
69+
@JsonSubTypes.Type(value = A_SIMPLE_NAME.class),
70+
})
71+
interface InterSimpleName {
72+
default void yes() {}
73+
}
74+
75+
enum A_SIMPLE_NAME implements InterSimpleName {
76+
A1,
77+
A2 {
78+
@Override
79+
public void yes() { }
80+
};
81+
}
82+
83+
private final ObjectMapper MAPPER = newJsonMapper();
84+
85+
@Test
86+
public void testIssue4733Class() throws Exception
87+
{
88+
String json1 = MAPPER.writeValueAsString(A_CLASS.A1);
89+
String json2 = MAPPER.writeValueAsString(A_CLASS.A2);
90+
91+
assertEquals(A_CLASS.A1, MAPPER.readValue(json1, A_CLASS.class));
92+
assertEquals(A_CLASS.A2, MAPPER.readValue(json2, A_CLASS.class));
93+
}
94+
95+
@Test
96+
public void testIssue4733MinimalClass() throws Exception
97+
{
98+
String json1 = MAPPER.writeValueAsString(A_MIN_CLASS.A1);
99+
String json2 = MAPPER.writeValueAsString(A_MIN_CLASS.A2);
100+
assertEquals(A_MIN_CLASS.A1, MAPPER.readValue(json1, A_MIN_CLASS.class),
101+
"JSON: "+json1);
102+
assertEquals(A_MIN_CLASS.A2, MAPPER.readValue(json2, A_MIN_CLASS.class),
103+
"JSON: "+json2);
104+
}
105+
106+
@Test
107+
public void testIssue4733Name() throws Exception
108+
{
109+
String json1 = MAPPER.writeValueAsString(A_NAME.A1);
110+
String json2 = MAPPER.writeValueAsString(A_NAME.A2);
111+
assertEquals(A_NAME.A1, MAPPER.readValue(json1, A_NAME.class),
112+
"JSON: "+json1);
113+
assertEquals(A_NAME.A2, MAPPER.readValue(json2, A_NAME.class),
114+
"JSON: "+json2);
115+
}
116+
117+
@Test
118+
public void testIssue4733SimpleName() throws Exception
119+
{
120+
String json1 = MAPPER.writeValueAsString(A_SIMPLE_NAME.A1);
121+
String json2 = MAPPER.writeValueAsString(A_SIMPLE_NAME.A2);
122+
assertEquals(A_SIMPLE_NAME.A1, MAPPER.readValue(json1, A_SIMPLE_NAME.class),
123+
"JSON: "+json1);
124+
assertEquals(A_SIMPLE_NAME.A2, MAPPER.readValue(json2, A_SIMPLE_NAME.class),
125+
"JSON: "+json2);
126+
}
127+
}

0 commit comments

Comments
 (0)