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.
The result is a class that you can use for your deserialization target. 1 Copy the JSON that you need to deserialize. 2 Create a class file and delete the template code. 3 Choose Edit > Paste Special > Paste JSON as Classes . The result is a class that you can use for your deserialization target.
But in our case it will fail to deserialize the data field, because Gson simply does not know which parameter type to use. How can we tell Gson about it? There is an alternative signature of the deserialization method—f romJson (String json, Type typeOfT) where Type is java.lang.reflect.
Contains elements that are serializable. The serializer calls the GetEnumerator () method, and writes the elements. Deserialization is more complicated and is not supported for some collection types. The following sections are organized by namespace and show which types are supported for serialization and deserialization. * See Supported key types.
There is an alternative signature of the deserialization method—f romJson (String json, Type typeOfT) where Type is java.lang.reflect. Type which may contain information about parametrization too. One of the ways to extract the type from parametrized class is to use Gson's TypeToken class, which uses reflection magic in order to do this.
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;
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With