Destruction of the generic JSON collection

I have 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; } 

My project has a Jackson library used to serialize and deserialize JSON.

How to configure AnswersDto (use special annotations) or AnswerDto (also annotations) classes to be able to properly deserialize a query using AnswersDto in your body, for example:

 { "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 maps the AnswerDto value of the object to the LinkedHashMap instead of the class object ( Location or JobTitle ). Should I write a custom JsonDeserializer<AnswerDto> or configuration using @JsonTypeInfo and @JsonSubTypes might be enough?

To properly deserialize a query with just one AnswerDto in the form

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

I use:

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

without any other custom configuration.

+5
source share
2 answers

I solved the problem 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; } 
+5
source

My suggestion is to create a separate interface for possible answer values โ€‹โ€‹and use @JsonTypeInfo . You can also omit the type field from AnswerDto , AnswerType enum, and the additional classes *AnswerDto becuse jackson will add you type information. 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 { /*..*/ } 

The json result will look 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" } } ] } 

Will be analyzed using

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

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

In other cases, you will need to create a custom deserializer for the AnswersDto class.

+1
source

All Articles