Search code examples
javajsonspringdeserializationgeneric-collections

JSON generic collection deserialization


I've such DTO classes written in Java:

public class AnswersDto {
    private String uuid;
    private Set<AnswerDto> answers;
}

public class AnswerDto<T> {
    private String uuid;
    private AnswerType type;
    private T value;
}

class LocationAnswerDto extends AnswerDto<Location> {
}

class JobTitleAnswerDto extends AnswerDto<JobTitle> {
}

public enum AnswerType {
    LOCATION,
    JOB_TITLE,
}

class Location {
    String text;
    String placeId;
}

class JobTitle {
    String id;
    String name;
}

In my project there is Jackson library used for serialization and deserialization of JSONs.

How to configure AnswersDto (use special annotations) or AnswerDto (annotation as well) classes to be able to properly deserialize request with AnswersDto in its body, e.g.:

{
    "uuid": "e82544ac-1cc7-4dbb-bd1d-bdbfe33dee73",
    "answers": [
        {
            "uuid": "e82544ac-1cc7-4dbb-bd1d-bdbfe33dee73",
            "type": "LOCATION",
            "value": {
                "text": "Dublin",
                "placeId": "121"
            }
        },
        {
            "uuid": "e82544ac-1cc7-4dbb-bd1d-bdbfe33dee73",
            "type": "JOB_TITLE",
            "value": {
                "id": "1",
                "name": "Developer"
            }
        }
    ]
}

Unfortunately Jackson by default maps value of AnswerDto object to LinkedHashMap instead of object of proper (Location or JobTitle) class type. Should I write custom JsonDeserializer<AnswerDto> or configuration by use of @JsonTypeInfo and @JsonSubTypes could be enough?

To properly deserialize request with just one AnswerDto in form of

{
    "uuid": "e82544ac-1cc7-4dbb-bd1d-bdbfe33dee73",
    "type": "LOCATION",
    "value": {
        "text": "Dublin",
        "placeId": "121"
    }
}

I'm using:

AnswerDto<Location> answerDto = objectMapper.readValue(jsonRequest, new TypeReference<AnswerDto<Location>>() {
});

without any other custom configuration.


Solution

  • I've resolved issue by using Jackson's custom annotations @JsonTypeInfo and @JsonSubTypes:

    public class AnswerDto<T> {
    
        private String uuid;
    
        private AnswerType type;
    
        @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "type")
        @JsonSubTypes({
                @JsonSubTypes.Type(value = Location.class, name = AnswerType.Types.LOCATION),
                @JsonSubTypes.Type(value = JobTitle.class, name = AnswerType.Types.JOB_TITLE)
        })
        private T value;
    }