Karthik Pala
Karthik Pala

Reputation: 168

Java: Deserialize a json to object in rest template using "@class" in json -SpringBoot

I have to instantiate a class which extends the abstract class from JSON using information in @class as shown below.

"name": {
  "health": "xxx",
  "animal": {
    "_class": "com.example.Dog",
    "height" : "20"
    "color" : "white"
  }
},

Here the abstract class is animal and dog extends the animal class. So using the information in @class, can we instantiate dog directly. Also this is the response I am getting in restTemplate

ResponseEntity<List<SomeListName>> response = restTemplate.exchange("http://10.150.15.172:8080/agencies", HttpMethod.GET, request, responseType);

The following error is coming when this line is executed. Since the POJO classes are auto-generated, I cannot use annotations like @JsonTypeInfo

I am using Spring boot and maven. This error is coming in console.

Could not read JSON: Can not construct instance of "MyPOJO", problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information

Upvotes: 4

Views: 16781

Answers (2)

Felipe
Felipe

Reputation: 7563

Just as an addition to the answer from @Master Slave. He said

you can customize the ObjectMapper for using the MixIns, Customizing the Jackson Object Mapper, note especially the mixIn method of Jackson2ObjectMapperBuilder

but he didn't define the ObjectMapper for the RestTemplate. Here is how his answer helped me to figure out my complete answer.

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.addMixIn(Target.class, MixinClass.class)
    .setVisibility(VisibilityChecker.Std.defaultInstance()
        .withFieldVisibility(JsonAutoDetect.Visibility.ANY)
        .withGetterVisibility(JsonAutoDetect.Visibility.ANY)
        .withSetterVisibility(JsonAutoDetect.Visibility.ANY)
        .withCreatorVisibility(JsonAutoDetect.Visibility.ANY)
    );
List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
MappingJackson2HttpMessageConverter jsonMessageConverter = new MappingJackson2HttpMessageConverter();
jsonMessageConverter.setObjectMapper(objectMapper);
messageConverters.add(jsonMessageConverter);
restTemplate.setMessageConverters(messageConverters);
MyResponse response =
    restTemplate.exchange(request, new ParameterizedTypeReference<MyResponse>() {
    }).getBody();

Upvotes: 1

Master Slave
Master Slave

Reputation: 28519

You can use @JsonTypeInfo annotation regardless of the fact that the classes are generated, by adhering to MixIn's

"mix-in annotations are": a way to associate annotations with classes, without modifying (target) classes themselves.

That is, you can:

Define that annotations of a mix-in class (or interface) will be used with a target class (or interface) such that it appears as if the target class had all annotations that the mix-in class has (for purposes of configuring serialization / deserialization)

So you can write your AnimalMixIn class, something like

@JsonTypeInfo(  
    use = JsonTypeInfo.Id.NAME,  
    include = JsonTypeInfo.As.PROPERTY,  
    property = "_class")  
@JsonSubTypes({  
    @Type(value = Cat.class, name = "com.example.Cat"),  
    @Type(value = Dog.class, name = "com.example.Dog") })  
abstract class AnimalMixIn  
{  

}  

and configure your deserializer

    ObjectMapper mapper = new ObjectMapper();  
    mapper.getDeserializationConfig().addMixInAnnotations(  
    Animal.class, AnimalMixIn.class);

Since you're using Spring Boot, you can check the following blog post to see how you can customize the ObjectMapper for using the MixIns, Customizing the Jackson Object Mapper, note especially the mixIn method of Jackson2ObjectMapperBuilder

Using customized ObjectMapper within RestTemplate should be set through converters, something like

    RestTemplate restTemplate = new RestTemplate();
    List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
    MappingJackson2HttpMessageConverter jsonMessageConverter = new MappingJackson2HttpMessageConverter();
    jsonMessageConverter.setObjectMapper(objectMapper);
    messageConverters.add(jsonMessageConverter);
    restTemplate.setMessageConverters(messageConverters);
    return restTemplate;

Upvotes: 8

Related Questions