JsonTypeInfo.As.EXTERNAL_PROPERTY
does not work with record wrappers · Issue #3342 · FasterXML/jackson-databind (original) (raw)
Describe the bug
When I try to use JsonTypeInfo.As.EXTERNAL_PROPERTY
inside a record, I get
com.fasterxml.jackson.databind.exc.ValueInstantiationException: Cannot construct instance of `my.company.fastcheck.analyzer.JacksonExternalTypeIdTest$Parent`, problem: Internal error: no creator index for property 'child' (of type com.fasterxml.jackson.databind.deser.impl.FieldProperty)
Note that it works with normal classes. Code examples below.
Version information
2.13.0
To Reproduce
Using a record as wrapping object: (Fails)
import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.databind.DatabindContext; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.annotation.JsonTypeIdResolver; import com.fasterxml.jackson.databind.jsontype.impl.TypeIdResolverBase; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test;
import java.io.IOException;
class JacksonExternalTypeIdTest {
@Test
void testExternalTypeIdPropertyInsideRecord() throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
Parent parent = objectMapper.readValue("""
{"type": "CHILLED", "child": {}}
""", Parent.class);
Assertions.assertTrue(parent.child instanceof ChilledChild);
}
public enum ParentType {
CHILLED,
AGGRESSIVE
}
public static record Parent(
ParentType type,
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "type")
@JsonTypeIdResolver(ChildBaseByParentTypeResolver.class)
ChildBase child
) {}
public static interface ChildBase {
}
public static record AggressiveChild(String someString) implements ChildBase {
}
public static record ChilledChild(String someString) implements ChildBase {
}
public static class ChildBaseByParentTypeResolver extends TypeIdResolverBase {
private JavaType superType;
@Override
public void init(JavaType baseType) {
superType = baseType;
}
@Override
public JsonTypeInfo.Id getMechanism() {
return JsonTypeInfo.Id.NAME;
}
@Override
public JavaType typeFromId(DatabindContext context, String id) {
Class<?> subType = switch (id) {
case "CHILLED" -> ChilledChild.class;
case "AGGRESSIVE" -> AggressiveChild.class;
default -> throw new IllegalArgumentException();
};
return context.constructSpecializedType(superType, subType);
}
@Override
public String idFromValue(Object value) {
throw new UnsupportedOperationException();
}
@Override
public String idFromValueAndType(Object value, Class<?> suggestedType) {
throw new UnsupportedOperationException();
}
}
}
Using a class as wrapping object: (Passes)
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.databind.DatabindContext; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.annotation.JsonTypeIdResolver; import com.fasterxml.jackson.databind.jsontype.impl.TypeIdResolverBase; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test;
import java.io.IOException;
class JacksonExternalTypeIdTest {
@Test
void testExternalTypeIdPropertyInsideRecord() throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
Parent parent = objectMapper.readValue("""
{"type": "CHILLED", "child": {}}
""", Parent.class);
Assertions.assertTrue(parent.child instanceof ChilledChild);
}
public enum ParentType {
CHILLED,
AGGRESSIVE
}
public static final class Parent {
private final ParentType type;
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "type")
@JsonTypeIdResolver(ChildBaseByParentTypeResolver.class)
private final ChildBase child;
public Parent(
@JsonProperty("type") ParentType type,
@JsonProperty("child") ChildBase child
) {
this.type = type;
this.child = child;
}
public ParentType type() {
return type;
}
public ChildBase child() {
return child;
}
}
public static interface ChildBase {
}
public static record AggressiveChild(String someString) implements ChildBase {
}
public static record ChilledChild(String someString) implements ChildBase {
}
public static class ChildBaseByParentTypeResolver extends TypeIdResolverBase {
private JavaType superType;
@Override
public void init(JavaType baseType) {
superType = baseType;
}
@Override
public JsonTypeInfo.Id getMechanism() {
return JsonTypeInfo.Id.NAME;
}
@Override
public JavaType typeFromId(DatabindContext context, String id) {
Class<?> subType = switch (id) {
case "CHILLED" -> ChilledChild.class;
case "AGGRESSIVE" -> AggressiveChild.class;
default -> throw new IllegalArgumentException();
};
return context.constructSpecializedType(superType, subType);
}
@Override
public String idFromValue(Object value) {
throw new UnsupportedOperationException();
}
@Override
public String idFromValueAndType(Object value, Class<?> suggestedType) {
throw new UnsupportedOperationException();
}
}
}
Expected behavior
Should work with records, too.
For now, using normal class as workaround.
Additional context
(none)