Matteo NNZ
Matteo NNZ

Reputation: 12665

Jackson can't read field declared in super class

I have this parent class:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "objectType")
@JsonSubTypes(
    {
        @JsonSubTypes.Type(value = Child.class, name = "child"),
    }
)
public class Parent {
    @JsonProperty("objectType")
    private ProductType productType

    public ProductType getProductType { return productType; }
}

This class is extended by a child class:

public class Child extends Parent {
    @JsonProperty("field1")
    private Field1 field1;
    @JsonProperty("field2")
    private Field2 field2;
}

I try to parse the following Json file representing a serialized instance of Child class:

{
    "objectType" : "child1",
    "field1" : "value1",
    "field2" : "value2"
}

... with the following code:

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enableDefaultTyping();
Parent parent = objectMapper.treeToValue(parentJsonNode, Parent.class);

(p.s. parentJsonNode is a JsonNode created by reading a JSON file).

What happens is that the field1 and field2 of my object are correctly read and set, however the field objectType (declared into the parent class) remains null after parsing.

What should I do to make Jackson read the value child of objectType correctly from the Json object?

Here is the code for the ProductType:

@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum ProductType {

    CHILD1("child1"), CHILD2("child2");

    private String objectType;

    private ProductType(String objectType) {
        this.objectType = objectType;
    }

    @JsonValue
    public String getObjectType() {  return objectType; }

}

Upvotes: 0

Views: 1961

Answers (1)

Smutje
Smutje

Reputation: 18143

This is due to the strange behaviour of Jackson to add an additional field non-explicitly visible to you during compile time to distinguish between the subclasses named like the value you put in @JsonTypeInfo. Because your parent class contains a property serialized into a JSON property named objectType and the name of the denominator given in @JsonTypeInfo also being objectType. If you would serialize an object of Child you would notice that the serialized object contains two fields with identical name objectType where only one of them would be filled:

System.out.println(objectMapper.writeValueAsString(new Child()));

leads to

{"objectType":"child","objectType":null,"field1":null,"field2":null}

The workaround is to either skip the explicit field productType (serialized as objectType) in the parent class if you don't need it in your code, or give it a different name for serialization (e.g. productType), but then you need to of course have a second JSON field to be deserialized into it:

Changing

public class Parent {

    @JsonProperty("objectType")
    private String productType;

to

public class Parent {

    @JsonProperty("productType")
    private String productType;

and

Parent parent = objectMapper.treeToValue(objectMapper.readTree("{\n" +
    "    \"objectType\" : \"child\",\n" +
    "    \"productType\" : \"child\",\n" +
    "    \"field1\" : \"value1\",\n" +
    "    \"field2\" : \"value2\"\n" +
    "}"), Parent.class);
System.out.println(parent.getProductType());

leads to the output

child

A third possibility would be to initialize the field explicitly, e.g. via constructor, because Jackson uses the default constructor to create the objects on deserialization:

// For subclasses
protected Parent (String productType) {
    this.productType = productType;
}

// For Jackson
public Parent () {
}

and

public Child () {
    super("child");
}

Edit: I used String instead of ProductType to ease up my code but the result is similar.

Upvotes: 2

Related Questions