yejianfengblue
yejianfengblue

Reputation: 2419

Multiple level @JsonTypeInfo and @JsonSubTypes

I have class hierarchy A <- B <- C. I don't want class A to be aware of C, so I intend to use A.type="B" to indicate it to be class B, and then B.type2="C" to indicate it to be class C.

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type")
@JsonSubTypes({@JsonSubTypes.Type(value = B.class, name = "B")})
public abstract class A {

    private final String type;

    public A(String type) {
        this.type = type;
    }
}

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type2")
@JsonSubTypes({@JsonSubTypes.Type(value = C.class, name = "C")})
public class B extends A {

    private final String type2;

    private final String propertyB;

    @JsonCreator
    public B(@JsonProperty("type2") String type2,
             @JsonProperty("propertyB") String propertyB) {
        super("B");
        this.type2 = type2;
        this.propertyB = propertyB;
    }
}

public class C extends B {

    private final String propertyC;

    @JsonCreator
    public C(@JsonProperty("propertyB") String propertyB,
             @JsonProperty("propertyC") String propertyC) {
        super("C", propertyB);
        this.propertyC = propertyC;
    }
}

When I read JSON of C to class A, the actual Java object is class B but not C.

@Test
void whenReadCJsonToA_thenObjectIsInstanceOfC() throws JsonProcessingException {

    String json = "{\n" +
                  "  \"type\" : \"B\",\n" +
                  "  \"type2\" : \"C\",\n" +
                  "  \"propertyB\" : \"b\",\n" +
                  "  \"propertyC\" : \"c\"\n" +
                  "}";

    A obj = objectMapper.readValue(json, A.class);
    assertTrue(obj instanceof B, "obj is not instance of B");  // pass
    assertTrue(obj instanceof C, "obj is not instance of C");  // fail
}

One way to make above test pass is writing custom deserializer, but this solution is tedious if the class holds many fields.

Is it possible to make above test pass with a more elegant way? Is my indention to cascade @JsonTypeInfo and @JsonSubTypes completely wrong?

My maven project can be found in Github.

Upvotes: 2

Views: 5332

Answers (1)

yejianfengblue
yejianfengblue

Reputation: 2419

According to this Jackson issue, multiple level inheritance is supported with only one type discliminator property. In my code, keep only property type and remove property type2.

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type")
@JsonSubTypes({@JsonSubTypes.Type(value = B.class, name = "B")})
public abstract class A {

    private final String type;

    public A(String type) {
        this.type = type;
    }
}

@JsonSubTypes({@JsonSubTypes.Type(value = C.class, name = "C")})
public class B extends A {

    private final String propertyB;

    @JsonCreator
    public B(@JsonProperty("propertyB") String propertyB) {
        super("B");
        this.propertyB = propertyB;
    }
}

public class C extends B {

    private final String propertyC;

    @JsonCreator
    public C(@JsonProperty("propertyB") String propertyB,
             @JsonProperty("propertyC") String propertyC) {
        super(propertyB);
        this.propertyC = propertyC;
    }
}

See full code in this commit.

This commit uses enum as type discliminator property type.

Upvotes: 2

Related Questions