Search code examples
javajsonjacksonjackson-databind

How to deserialize JSON via JsonTypeInfo with unknown property


I need to deserialize JSON looks like the following:

{
  "data": [{
    "id": "id1",
    "type": "type1"
    "name": "John",
    ...
  },
  {
    "id": "id2",
    "type": "type2",
    "name": "Rebecca",
    ...
  },
  {
    "id": "id3",
    "type": "unknown",
    "name": "Peter",
    ...
  }]
}

For deserializing JSON which I have written above I have created a couple of classes:

@JsonTypeInfo(
        use = JsonTypeInfo.Id.NAME,
        property = "type",
        defaultImpl = DefaultData.class
)
@JsonSubTypes({
        @JsonSubTypes.Type(value = Type1Data.class, name = "type1"),
        @JsonSubTypes.Type(value = Type2Data.class, name = "type2")
})
public class AbstractData {
    public final String id;
    public final String type;
    public final String name;
    public AbstractData(String id, String type, String name) {
        this.id = id;
        this.type = type;
        this.name = name;
    }
}

public class Type1Data extends AbstractData {
    @JsonCreator
    public Type1Data(@JsonProperty("id") String id,
                     @JsonProperty("name") String name
    ) {
        super(id, "type1", name);
    }
}

public class DefaultData extends AbstractData {
    @JsonCreator
    public DefaultData(@JsonProperty("id") String id, 
                       @JsonProperty("type") String type,
                       @JsonProperty("name") String name
    ) {
        super(id, type, name);
    }
}

public class Main {
    public static void main(String... args) {
        ObjectMapper mapper = new ObjectMapper();
        AbstractData data = mapper.readValue(json, AbstractData.class);
    }
}

I get an exception if I use default implementation:

com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve type id 'unknown' as a type

The class DefaultData I need to avoid a deserialization exception if I will get the unknown type.

How can I fix this issue?


Solution

  • Summary

    Right now it is not clear what is the exact root cause of the problem, because your example works for me with several corrections.

    Still, please, consider the corrections as a draft.

    Corrections

    Data class for root object: Introduced

    import com.fasterxml.jackson.annotation.JsonCreator;
    import com.fasterxml.jackson.annotation.JsonProperty;
    import java.util.Arrays;
    import java.util.StringJoiner;
    
    public class RootData {
        public final AbstractData[] data;
    
        @JsonCreator
        public RootData(@JsonProperty("data") final AbstractData[] data) {
            this.data = data;
        }
    
        @Override
        public String toString() {
            return new StringJoiner(", ", RootData.class.getSimpleName() + "[", "]")
                .add("data=" + Arrays.toString(data))
                .toString();
        }
    }
    

    AbstractData data class: Updated to deserialize type property

    Please, see the Javadoc:

    Note on visibility of type identifier: by default, deserialization (use during reading of JSON) of type identifier is completely handled by Jackson, and is not passed to deserializers. However, if so desired, it is possible to define property visible = true in which case property will be passed as-is to deserializers (and set via setter or field) on deserialization.

    Updated annotation by adding visible = true:

    @JsonTypeInfo(
        use = JsonTypeInfo.Id.NAME,
        property = "type",
        visible = true,
        defaultImpl = DefaultData.class
    )
    

    Additionally, please, see the related question: java - Jackson - @JsonTypeInfo property is being mapped as null?.

    Data classes: Implemented toString() method

    (Omitted.)

    Main: Updated to use root data class

    Please, note that I have corrected the JSON document: added the missing comma after "type": "type1".

    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.ObjectMapper;
    
    public class Main {
        public static void main(String... args) throws JsonProcessingException {
            final String jsonDocumentString =
                """
                {
                  "data": [{
                    "id": "id1",
                    "type": "type1",
                    "name": "John"
                  },
                  {
                    "id": "id2",
                    "type": "type2",
                    "name": "Rebecca"
                  },
                  {
                    "id": "id3",
                    "type": "unknown",
                    "name": "Peter"
                  }]
                }
                """;
    
            final ObjectMapper mapper = new ObjectMapper();
            final RootData rootData = mapper.readValue(jsonDocumentString, RootData.class);
            System.out.println(rootData);
        }
    }
    

    The program completes successfully, i.e. without an exception being thrown.
    The program outputs:

    RootData[data=[Type1Data[id='id1', type='type1', name='John'], Type2Data[id='id2', type='type2', name='Rebecca'], AbstractData[id='id3', type='unknown', name='Peter']]]
    

    The actual result (output) is the same as the expected result.