java - Jackson Polymorphic Deserialization with multiple abstract class - Stack Overflow

admin2025-04-17  0

I have a class hierarchy where:

  • AbstractParent is the top-level abstract class.
  • AbstractChild extends AbstractParent (1..n subclasses).
  • ConcreteObj extends AbstractChild (1..n concrete implementations).

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.

Class Structure

Abstract Parent

@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;
    }
}

Abstract Child

@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;
    }
}

Concrete Class

public class ConcreteObjX extends AbstractChild {
    private String someProperty;

    public ConcreteObjX() {}

    public ConcreteObjX(String key1, String key2, String someProperty) {
        super(key1, key2);
        this.someProperty = someProperty;
    }
}

JSON Input

{
  "key1": "CHILD_A",
  "key2": "CONCRETE_X",
  "someProperty": "test-value"
}

Issue

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.

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.

Question

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

  1. Ensured both abstract classes have @JsonTypeInfo and @JsonSubTypes.

  2. Verified that ConcreteObjX is properly registered under AbstractChild.

  3. Added default constructors in all abstract and concrete classes.

  4. Attempted using a custom deserializer, but it still fails.

I have a class hierarchy where:

  • AbstractParent is the top-level abstract class.
  • AbstractChild extends AbstractParent (1..n subclasses).
  • ConcreteObj extends AbstractChild (1..n concrete implementations).

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.

Class Structure

Abstract Parent

@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;
    }
}

Abstract Child

@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;
    }
}

Concrete Class

public class ConcreteObjX extends AbstractChild {
    private String someProperty;

    public ConcreteObjX() {}

    public ConcreteObjX(String key1, String key2, String someProperty) {
        super(key1, key2);
        this.someProperty = someProperty;
    }
}

JSON Input

{
  "key1": "CHILD_A",
  "key2": "CONCRETE_X",
  "someProperty": "test-value"
}

Issue

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.

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.

Question

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

  1. Ensured both abstract classes have @JsonTypeInfo and @JsonSubTypes.

  2. Verified that ConcreteObjX is properly registered under AbstractChild.

  3. Added default constructors in all abstract and concrete classes.

  4. Attempted using a custom deserializer, but it still fails.

Share asked Mar 10 at 8:16 Aditya GaddhyanAditya Gaddhyan 4142 silver badges17 bronze badges
Add a comment  | 

2 Answers 2

Reset to default 2

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:

  • Add custom deserializer:
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);
    }
}
  • Annotate the root of class hierarchy (e.g. AbstractParent) with @JsonDeserialize(using = AbstractParentDeserializer.class)
  • Annotate 2nd level classes (e.g. AbstractChildA, AbstractChildB) with @JsonDeserialize without specifying a custom deserializer to make Jackson use default deserializer for them and their children.
    Other options to make Jackson use default deserializer could be found here How do I call the default deserializer from a custom deserializer in Jackson.
    But if you are ok to set all fields manually, then no need to annotate 2nd level classes with @JsonDeserialize, just change deserializer
AbstractParent 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")
})
转载请注明原文地址:http://conceptsofalgorithm.com/Algorithm/1744856900a270881.html

最新回复(0)