Search code examples
javajsonspring-bootjacksondeserialization

Spring Boot Jackson Not Deserializing Parent Fields When Using JsonSubTypes


I have the following classes:

public class TestClass {

    public ParentClass parentClass;

    public ParentClass getParentClass() {
        return parentClass;
    }

    public void setParentClass(ParentClass parentClass) {
        this.parentClass = parentClass;
    }

    @Override
    public String toString() {
        return "TestClass [parentClass=" + parentClass + "]";
    }

}

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
@JsonSubTypes({
        @JsonSubTypes.Type(value = ChildClass1.class, name = "childClass1"),
        @JsonSubTypes.Type(value = ChildClass2.class, name = "childClass2")
})
public class ParentClass {

    @JsonProperty
    public String type;

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    @Override
    public String toString() {
        return "ParentClass [type=" + type + "]";
    }

}

public class ChildClass1 extends ParentClass {
    public String childClass1Field;

    public String getChildClass1Field() {
        return childClass1Field;
    }

    public void setChildClass1Field(String childClass1Field) {
        this.childClass1Field = childClass1Field;
    }

    @Override
    public String toString() {
        return "ChildClass1 [childClass1Field=" + childClass1Field + ", type=" + type + "]";
    }


}

public class ChildClass2 extends ParentClass {
    public String childClass2Field;

    public String getChildClass2Field() {
        return childClass2Field;
    }

    public void setChildClass2Field(String childClass2Field) {
        this.childClass2Field = childClass2Field;
    }

    @Override
    public String toString() {
        return "ChildClass2 [childClass2Field=" + childClass2Field + ", type=" + type + "]";
    }


}

when I have the following payload:

[{
  "parentClass": {
      "type": "childClass1",
      "childClass1Field": "my child class 1 field value"
  }
}]

It de-serializes to the following (print out of the deserialized object):

[TestClass [parentClass=ChildClass1 [childClass1Field=my child class 1 field value, type=null]]]

Polymorphism works correctly as it is instantiating the correct child class (ChildClass1) but notice the type field from the parentclass is not de-serialized properly and is returned back as 'null' instead of the expected value of 'childClass1'

I've tried everything under the sun but all to no avail. Any help would be appreciated!


Solution

  • You were close to the solution that can be obtained with adding two properties in your existing annotation: the JsonTypeInfo.As#EXISTING_PROPERTY property and also the JsonTypeInfo#visible property that both have to be included in your JsonTypeInfo annotation you are using like below:

    @Data
    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = As.EXISTING_PROPERTY, 
    property = "type", visible = true)
    @JsonSubTypes({
            @JsonSubTypes.Type(value = ChildClass1.class, name = "childClass1")
    })
    public class ParentClass {
        protected String type;
    }
    
    @Getter
    @Setter
    public class ChildClass1 extends ParentClass {
        private String childClass1Field;
        
        @Override
        public String toString() {
            return "ChildClass1 [childClass1Field=" + childClass1Field + ", type=" + type + "]";
        }
    }
    

    Then you can deserialize your json obtaining the expected result:

    String json = """
                     {
                       "type": "childClass1",
                       "childClass1Field": "my child class 1 field value"
                     }
                  """;
    ParentClass parentClass = mapper.readValue(json, ParentClass.class);
    //it prints ChildClass1 [childClass1Field=my child class 1 field value, type=childClass1]
    System.out.println(parentClass);