6

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.

vsminkov
  • 10,912
  • 2
  • 38
  • 50
Bananan
  • 613
  • 5
  • 19
  • You have a collection of aswer objects but you want to deserialize to only one answer object ? What about using AnswersDto answers = objectMapper.readValue(jsonRequest, new TypeReference() {}); – reos Sep 01 '16 at 15:10
  • I want to map all objects from `answers` node to `Set` collection. When I use `objectMapper` with this `TypeReference` than unfortunately `value` property of every `AnswerDto` from collection is mapped to `LinkedHashMap`. – Bananan Sep 01 '16 at 15:21

2 Answers2

5

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;
}
Bananan
  • 613
  • 5
  • 19
1

My suggestion is to make a separate interface for possible answer values and use @JsonTypeInfo on it. You can also drop type field from AnswerDto, AnswerType enum, and additional *AnswerDto classes becuse jackson will add type info for you. Like this

public class AnswerDto<T extends AnswerValue> {
    private String uuid;
    private T value;
}

@JsonTypeInfo(use = Id.CLASS, include = As.PROPERTY)
interface AnswerValue {}

class Location implements AnswerValue { /*..*/ }
class JobTitle implements AnswerValue { /*..*/ }

Resulting json will looks like this

{
  "uuid": "e82544ac-1cc7-4dbb-bd1d-bdbfe33dee73",
  "answers": [
    {
      "uuid": "e82544ac-1cc7-4dbb-bd1d-bdbfe33dee73",
      "value": {
        "@class": "com.demo.Location",
        "text": "Dublin",
        "placeId": "121"
      }
    },
    {
      "uuid": "e82544ac-1cc7-4dbb-bd1d-bdbfe33dee73",
      "value": {
        "@class": "com.demo.JobTitle",
        "id": "1",
        "name": "Developer"
      }
    }
  ]
}

Which will be parsed using

AnswersDto answersDto = objectMapper.readValue(json, AnswersDto.class);

But this solution applies only in cases when you are a producer of json data and you do not have to think about backward compatibility.

In other cases you'll have to make custom desetializer for AnswersDto class.

vsminkov
  • 10,912
  • 2
  • 38
  • 50