Skip to content

Commit

Permalink
Merge branch '2.19'
Browse files Browse the repository at this point in the history
  • Loading branch information
cowtowncoder committed Nov 5, 2024
2 parents 99a88cb + 85f4b55 commit 5b7b628
Show file tree
Hide file tree
Showing 7 changed files with 181 additions and 13 deletions.
5 changes: 5 additions & 0 deletions release-notes/VERSION-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ Project: jackson-databind
(requested by @nathanukey)
(fix by Joo-Hyuk K)

2.18.2 (not yet released)

#4733: Wrong serialization of Type Ids for certain types of Enum values
(reported by @nlisker)

2.18.1 (28-Oct-2024)

#4741: When `Include.NON_DEFAULT` setting is used on POJO, empty values
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,17 +78,7 @@ protected JavaType _typeFromId(DatabindContext ctxt, String id) throws JacksonEx

protected String _idFrom(DatabindContext ctxt, Object value, Class<?> cls)
{
// Need to ensure that "enum subtypes" work too
if (ClassUtil.isEnumType(cls)) {
// 29-Sep-2019, tatu: `Class.isEnum()` only returns true for main declaration,
// but NOT from sub-class thereof (extending individual values). This
// is why additional resolution is needed: we want class that contains
// enumeration instances.
if (!cls.isEnum()) {
// and this parent would then have `Enum.class` as its parent:
cls = cls.getSuperclass();
}
}
cls = _resolveToParentAsNecessary(cls);
String str = cls.getName();
if (str.startsWith(JAVA_UTIL_PKG)) {
// 25-Jan-2009, tatu: There are some internal classes that we cannot access as is.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,23 @@ public static MinimalClassNameIdResolver construct(JavaType baseType,
@Override
public String idFromValue(DatabindContext ctxt, Object value)
{
String n = value.getClass().getName();
return idFromValueAndType(ctxt, value, value.getClass());
}

@Override
public String idFromValueAndType(DatabindContext ctxt, Object value, Class<?> rawType) {
// 04-Nov-2024, tatu: [databind#4733] Need to resolve enum sub-classes
// same way "ClassNameIdResolver" does
rawType = _resolveToParentAsNecessary(rawType);
String n = rawType.getName();
if (n.startsWith(_basePackagePrefix)) {
// note: we will leave the leading dot in there
return n.substring(_basePackagePrefix.length()-1);
}
return n;

}

@Override
protected JavaType _typeFromId(DatabindContext ctxt, String id) throws JacksonException
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ protected String idFromClass(DatabindContext ctxt, Class<?> cls)
if (cls == null) {
return null;
}
// 04-Nov-2024, tatu: [databind#4733] Need to resolve enum sub-classes
// same way "ClassNameIdResolver" does
cls = _resolveToParentAsNecessary(cls);

// 12-Oct-2019, tatu: This looked weird; was done in 2.x to force application
// of `TypeModifier`. But that just... does not seem right, at least not in
// the sense that raw class would be changed (intent for modifier is to change
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import tools.jackson.databind.DatabindContext;
import tools.jackson.databind.JavaType;
import tools.jackson.databind.jsontype.TypeIdResolver;
import tools.jackson.databind.util.ClassUtil;

/**
* Partial base implementation of {@link TypeIdResolver}: all custom implementations
Expand Down Expand Up @@ -67,4 +68,32 @@ public JavaType typeFromId(DatabindContext context, String id) throws JacksonEx
public String getDescForKnownTypeIds() {
return null;
}

/**
* Helper method for ensuring we properly resolve cases where we don't
* want to use given instance class due to it being a specific inner class
* but rather enclosing (or parent) class. Specific case we know of
* currently are "enum subtypes", cases
* where simple Enum constant has overrides and uses generated sub-class
* if parent Enum type. In this case we need to ensure that we use
* the main/parent Enum type, not sub-class.
*
* @param cls Class to check and possibly resolve
* @return Resolved class to use
* @since 2.18.2
*/
protected Class<?> _resolveToParentAsNecessary(Class<?> cls) {
// Need to ensure that "enum subtypes" work too
if (ClassUtil.isEnumType(cls)) {
// 29-Sep-2019, tatu: `Class.isEnum()` only returns true for main declaration,
// but NOT from sub-class thereof (extending individual values). This
// is why additional resolution is needed: we want class that contains
// enumeration instances.
if (!cls.isEnum()) {
// and this parent would then have `Enum.class` as its parent:
cls = cls.getSuperclass();
}
}
return cls;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ protected String idFromClass(DatabindContext ctxt, Class<?> cls)
if (cls == null) {
return null;
}
// 04-Nov-2024, tatu: [databind#4733] Need to resolve enum sub-classes
// same way "ClassNameIdResolver" does
cls = _resolveToParentAsNecessary(cls);

// 12-Oct-2019, tatu: This looked weird; was done in 2.x to force application
// of `TypeModifier`. But that just... does not seem right, at least not in
// the sense that raw class would be changed (intent for modifier is to change
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package tools.jackson.databind.jsontype.jdk;

import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;

import tools.jackson.databind.*;
import tools.jackson.databind.testutil.DatabindTestUtil;

import static org.junit.jupiter.api.Assertions.assertEquals;

import org.junit.jupiter.api.Test;

public class EnumTyping4733Test extends DatabindTestUtil
{
// Baseline case that already worked
@JsonTypeInfo(use = Id.CLASS)
@JsonSubTypes({
@JsonSubTypes.Type(value = A_CLASS.class),
})
interface InterClass {
default void yes() {}
}

enum A_CLASS implements InterClass {
A1,
A2 {
@Override
public void yes() { }
};
}

// Failed before fix for [databind#4733]
@JsonTypeInfo(use = Id.MINIMAL_CLASS)
@JsonSubTypes({
@JsonSubTypes.Type(value = A_MIN_CLASS.class),
})
interface InterMinimalClass {
default void yes() {}
}

enum A_MIN_CLASS implements InterMinimalClass {
A1,
A2 {
@Override
public void yes() { }
};
}

// Failed before fix for [databind#4733]
@JsonTypeInfo(use = Id.NAME)
@JsonSubTypes({
@JsonSubTypes.Type(value = A_NAME.class),
})
interface InterName {
default void yes() {}
}

enum A_NAME implements InterName {
A1,
A2 {
@Override
public void yes() { }
};
}

// Failed before fix for [databind#4733]
@JsonTypeInfo(use = Id.SIMPLE_NAME)
@JsonSubTypes({
@JsonSubTypes.Type(value = A_SIMPLE_NAME.class),
})
interface InterSimpleName {
default void yes() {}
}

enum A_SIMPLE_NAME implements InterSimpleName {
A1,
A2 {
@Override
public void yes() { }
};
}

private final ObjectMapper MAPPER = newJsonMapper();

@Test
public void testIssue4733Class() throws Exception
{
String json1 = MAPPER.writeValueAsString(A_CLASS.A1);
String json2 = MAPPER.writeValueAsString(A_CLASS.A2);

assertEquals(A_CLASS.A1, MAPPER.readValue(json1, A_CLASS.class));
assertEquals(A_CLASS.A2, MAPPER.readValue(json2, A_CLASS.class));
}

@Test
public void testIssue4733MinimalClass() throws Exception
{
String json1 = MAPPER.writeValueAsString(A_MIN_CLASS.A1);
String json2 = MAPPER.writeValueAsString(A_MIN_CLASS.A2);
assertEquals(A_MIN_CLASS.A1, MAPPER.readValue(json1, A_MIN_CLASS.class),
"JSON: "+json1);
assertEquals(A_MIN_CLASS.A2, MAPPER.readValue(json2, A_MIN_CLASS.class),
"JSON: "+json2);
}

@Test
public void testIssue4733Name() throws Exception
{
String json1 = MAPPER.writeValueAsString(A_NAME.A1);
String json2 = MAPPER.writeValueAsString(A_NAME.A2);
assertEquals(A_NAME.A1, MAPPER.readValue(json1, A_NAME.class),
"JSON: "+json1);
assertEquals(A_NAME.A2, MAPPER.readValue(json2, A_NAME.class),
"JSON: "+json2);
}

@Test
public void testIssue4733SimpleName() throws Exception
{
String json1 = MAPPER.writeValueAsString(A_SIMPLE_NAME.A1);
String json2 = MAPPER.writeValueAsString(A_SIMPLE_NAME.A2);
assertEquals(A_SIMPLE_NAME.A1, MAPPER.readValue(json1, A_SIMPLE_NAME.class),
"JSON: "+json1);
assertEquals(A_SIMPLE_NAME.A2, MAPPER.readValue(json2, A_SIMPLE_NAME.class),
"JSON: "+json2);
}
}

0 comments on commit 5b7b628

Please sign in to comment.