I have a class hierarchy where:
Both abstract classes use Jackson’s polymorphic deserialization, but they rely on different discriminator keys:
AbstractParent
uses key1
to determine the correct AbstractChild
subtype.AbstractChild
uses key2
to determine the correct ConcreteObj
.@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "key1")
@JsonSubTypes({
@JsonSubTypes.Type(value = AbstractChildA.class, name = "CHILD_A"),
@JsonSubTypes.Type(value = AbstractChildB.class, name = "CHILD_B")
})
public abstract class AbstractParent {
private String key1;
public AbstractParent() {}
public AbstractParent(String key1) {
this.key1 = key1;
}
}
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "key2")
@JsonSubTypes({
@JsonSubTypes.Type(value = ConcreteObjX.class, name = "CONCRETE_X"),
@JsonSubTypes.Type(value = ConcreteObjY.class, name = "CONCRETE_Y")
})
public abstract class AbstractChild extends AbstractParent {
private String key2;
public AbstractChild() {}
public AbstractChild(String key1, String key2) {
super(key1);
this.key2 = key2;
}
}
public class ConcreteObjX extends AbstractChild {
private String someProperty;
public ConcreteObjX() {}
public ConcreteObjX(String key1, String key2, String someProperty) {
super(key1, key2);
this.someProperty = someProperty;
}
}
{
"key1": "CHILD_A",
"key2": "CONCRETE_X",
"someProperty": "test-value"
}
Jackson fails to deserialize this structure and throws the following error:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException:
Cannot construct instance of `AbstractChild`
(no Creators, like default constructor, exist):
abstract types either need to be mapped to concrete types, have a custom deserializer, or contain additional type information.
@JsonTypeInfo
and @JsonSubTypes
.ConcreteObjX
is properly registered under AbstractChild
.How can I configure Jackson to correctly deserialize this multi-level polymorphic hierarchy where each abstract class has its own discriminator key?
Would a custom deserializer be required, or is there a way to make Jackson handle this out of the box?
What I Have Tried
Ensured both abstract classes have @JsonTypeInfo and @JsonSubTypes.
Verified that ConcreteObjX is properly registered under AbstractChild.
Added default constructors in all abstract and concrete classes.
Attempted using a custom deserializer, but it still fails.
I have a class hierarchy where:
Both abstract classes use Jackson’s polymorphic deserialization, but they rely on different discriminator keys:
AbstractParent
uses key1
to determine the correct AbstractChild
subtype.AbstractChild
uses key2
to determine the correct ConcreteObj
.@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "key1")
@JsonSubTypes({
@JsonSubTypes.Type(value = AbstractChildA.class, name = "CHILD_A"),
@JsonSubTypes.Type(value = AbstractChildB.class, name = "CHILD_B")
})
public abstract class AbstractParent {
private String key1;
public AbstractParent() {}
public AbstractParent(String key1) {
this.key1 = key1;
}
}
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "key2")
@JsonSubTypes({
@JsonSubTypes.Type(value = ConcreteObjX.class, name = "CONCRETE_X"),
@JsonSubTypes.Type(value = ConcreteObjY.class, name = "CONCRETE_Y")
})
public abstract class AbstractChild extends AbstractParent {
private String key2;
public AbstractChild() {}
public AbstractChild(String key1, String key2) {
super(key1);
this.key2 = key2;
}
}
public class ConcreteObjX extends AbstractChild {
private String someProperty;
public ConcreteObjX() {}
public ConcreteObjX(String key1, String key2, String someProperty) {
super(key1, key2);
this.someProperty = someProperty;
}
}
{
"key1": "CHILD_A",
"key2": "CONCRETE_X",
"someProperty": "test-value"
}
Jackson fails to deserialize this structure and throws the following error:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException:
Cannot construct instance of `AbstractChild`
(no Creators, like default constructor, exist):
abstract types either need to be mapped to concrete types, have a custom deserializer, or contain additional type information.
@JsonTypeInfo
and @JsonSubTypes
.ConcreteObjX
is properly registered under AbstractChild
.How can I configure Jackson to correctly deserialize this multi-level polymorphic hierarchy where each abstract class has its own discriminator key?
Would a custom deserializer be required, or is there a way to make Jackson handle this out of the box?
What I Have Tried
Ensured both abstract classes have @JsonTypeInfo and @JsonSubTypes.
Verified that ConcreteObjX is properly registered under AbstractChild.
Added default constructors in all abstract and concrete classes.
Attempted using a custom deserializer, but it still fails.
I don’t see a way to achieve what you want using @JsonTypeInfo
and @JsonSubTypes
while having different discriminator key names at different levels of the abstract class hierarchy.
In the GitHub issue, it is stated that multi-level type resolution does not exist and is not planned.
So I suggest to implement custom deserializer to resolve your problem:
class AbstractParentDeserializer extends JsonDeserializer<AbstractParent> {
@Override
public AbstractParent deserialize(JsonParser jp, DeserializationContext ctxt) throws java.io.IOException {
JsonNode node = jp.getCodec().readTree(jp);
String key1 = node.has("key1") ? node.get("key1").asText() : null;
String key2 = node.has("key2") ? node.get("key2").asText() : null;
if (key1 == null || key2 == null) {
throw new IllegalArgumentException("Missing required fields: key1 or key2");
}
Class<? extends AbstractParent> valueType;
if ("CHILD_A".equals(key1) && "CONCRETE_X".equals(key2)) {
valueType = ConcreteObjX.class;
} else if ("CHILD_B".equals(key1) && "CONCRETE_Y".equals(key2)) {
valueType = ConcreteObjY.class;
} else {
throw new IllegalArgumentException("Unknown key combination: key1=" + key1 + ", key2=" + key2);
}
return jp.getCodec().treeToValue(node, valueType);
}
}
AbstractParent
) with @JsonDeserialize(using = AbstractParentDeserializer.class)
AbstractChildA
, AbstractChildB
) with @JsonDeserialize
without specifying a custom deserializer to make Jackson use default deserializer for them and their children. @JsonDeserialize
, just change deserializerAbstractParent result;
if ("CHILD_A".equals(key1) && "CONCRETE_X".equals(key2)) {
result = new ConcreteObjX(key1, key2, node.get("someProperty").asText());
} else if ("CHILD_B".equals(key1) && "CONCRETE_Y".equals(key2)) {
result = new ConcreteObjY(key1, key2, node.get("concretePropY").asText());
} else {
throw new IllegalArgumentException("Unknown key combination: key1=" + key1 + ", key2=" + key2);
}
return result;
The following config tells jackson that when it encounters { "key1": "CHILD_A" }
that it can instantiate and populate an instance of AbstractChildA
. But since AbstractChildA
is abstract this is invalid config.
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "key1")
@JsonSubTypes({
@JsonSubTypes.Type(value = AbstractChildA.class, name = "CHILD_A"),
@JsonSubTypes.Type(value = AbstractChildB.class, name = "CHILD_B")
})