ustad
ustad

Reputation: 539

Spring Boot (2.3.6.RELEASE) Deserialization Fails when using RestTemplate and Unwrap Root

I'm trying to consume an API using RestTemplate but it will simply not deserialize the json response into my pojo Here is the json payload I'm trying to deserialize:

"Response": {
        "Count": 77,
        "Data": [
            {
                "AllowDelete": "1",
                "ContactCount": 1482,
                "CreatedDate": "Dec 01, 2020",
                "ID": "17991951",
                "IsImporting": "0",
                "IsMasterUnsubscribe": "0",
                "ListAudited": "1",
                "ListDescription": "City of Markham Staff - December 2020 (LATEST)",
                "ListImportV3": "1",
                "ListType": "0",
                "ModifiedDate": "Dec 03, 2020",
                "Name": "City of Markham Staff - December 2020 (LATEST)",
                "NameShort": "City of Markham Staff - December 2020 (LATEST)",
                "PermissionPassList": "0",
                "Segments": [],
                "Status": ""
            },{
                "AllowDelete": "0",
                "ContactCount": 884,
                "CreatedDate": "Nov 04, 2011",
                "ID": "582203",
                "IsImporting": "0",
                "IsMasterUnsubscribe": "1",
                "ListAudited": "1",
                "ListDescription": "Master Unsubscribe List",
                "ListImportV3": "0",
                "ListType": "0",
                "ModifiedDate": "Dec 04, 2020",
                "Name": "Master Unsubscribe List",
                "NameShort": "Master Unsubscribe List",
                "PermissionPassList": "0",
                "Segments": [],
                "Status": ""
            }
        ],
        "Status": "1"
    }
}

Here is my main pojo:

package com.markham.enews.model;

import java.util.List;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonRootName;

@JsonIgnoreProperties(ignoreUnknown = true)
@JsonRootName(value = "Response")
public class Contact {

    //Total number
    private int count;

    //1 if successful, -1 if error
    private String status;

    // Further details of the Contact List
    private List<ContactFullRecord> data;

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public List<ContactFullRecord> getData() {
        return data;
    }

    public void setData(List<ContactFullRecord> data) {
        this.data = data;
    }

    @Override
    public String toString() {
        return "Contact [count=" + count + ", status=" + status + ", data=" + data + "]";
    }

}

As per this stack overflow link Spring Boot Jackson with Root name

I added the following to my application.properties:

spring.jackson.mapper.accept-case-insensitive-properties=true
spring.jackson.deserialization.unwrap-root-value=true

My rest controller get method is as follows:

@GetMapping(value = "/ContactTest")
private Contact getContactTest() {

    String uri = "https://clientapi.benchmarkemail.com/Contact/";
    RestTemplate restTemplate = new RestTemplate();

    HttpEntity<String> request = new HttpEntity<String>(createHeaders());
    ResponseEntity<Contact> response = restTemplate.exchange(uri, HttpMethod.GET, request, Contact.class);

    Contact contact = response.getBody();

    return contact;
}

But the resulting object has all empty/null values: "count": 0, "status": null, "data": null

I think the unwrap root and/or case insensitive properties are not being picked up.. If I write the following unit test and use objectMapper directly, it works:

    @Test
public void wrapRootValue() throws Exception {
    ObjectMapper mapper = new ObjectMapper();
    mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
    mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
    String str = "{    \"Response\": {\"Count\": 77,\"Data\": [{\"AllowDelete\": \"0\",\"ContactCount\": 884,\"CreatedDate\": \"Nov 04, 2011\",\"ID\": \"582203\",\"IsImporting\": \"0\",\"IsMasterUnsubscribe\": \"1\",\"ListAudited\": \"1\",\"ListDescription\": \"Master Unsubscribe List\",\"ListImportV3\": \"0\",\"ListType\": \"0\",\"ModifiedDate\": \"Dec 03, 2020\",\"Name\": \"Master Unsubscribe List\",\"NameShort\": \"Master Unsubscribe List\",\"PermissionPassList\": \"0\",\"Segments\": [],\"Status\": \"\"}],\"Status\": \"1\"}}";
    Contact root = mapper.readValue(str, Contact.class);
    System.out.println(root);
}

Output:

Contact [count=77, status=1, data=[ContactFullRecord [id=582203, name=Master Unsubscribe List, nameShort=Master Unsubscribe List, status=, contactCount=884.0, createdDate=Nov 04, 2011, modifiedDate=Dec 03, 2020, permissionPassList=0, listAudited=1, listDescription=Master Unsubscribe List, isImporting=0, isMasterUnsubscribe=1, allowDelete=0, listImportV3=0]]]

Any help would be greatly appreciated!

Upvotes: 2

Views: 1974

Answers (2)

s7vr
s7vr

Reputation: 75964

Use spring boot pre configured RestTemplateBuilder ( has all the jackson message converter configuration applied ) and use build to request new RestTemplate instance.

@Configuration
public class RestTemplateConfig {

   @Bean
   public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
      return restTemplateBuilder.build();
   }

}

Autowire the instance into controller class.

@Autowired 
private RestTemplate restTemplate;

@GetMapping(value = "/ContactTest")
private Contact getContactTest() {

    String uri = "https://clientapi.benchmarkemail.com/Contact/";

    HttpEntity<String> request = new HttpEntity<String>(createHeaders());
    ResponseEntity<Contact> response = restTemplate.exchange(uri, HttpMethod.GET, request, Contact.class);

    Contact contact = response.getBody();

    return contact;
}

You can also look at https://www.baeldung.com/spring-rest-template-builder for other set ups.

Upvotes: 4

jccampanero
jccampanero

Reputation: 53421

The problem is that you are configuring the Jackson deserialization behavior at the Spring Boot level, you are not configuring the deserialization behavior for your RestTemplate.

One possible approach you can follow is the one suggested by @s7vr in his/her answer, and reuse the Spring Boot provided configuration.

If you only want to customize the Jackson configuration for your RestTemplate you can do it with something like:

final List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();

// Base converters
messageConverters.add(new ByteArrayHttpMessageConverter());
messageConverters.add(new StringHttpMessageConverter());
messageConverters.add(new ResourceHttpMessageConverter(false));
messageConverters.add(new SourceHttpMessageConverter<>());
messageConverters.add(new AllEncompassingFormHttpMessageConverter());

// Custom Jackson Converter
final MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
final ObjectMapper mapper = mappingJackson2HttpMessageConverter.getObjectMapper();
mapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE);
mapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES);
messageConverters.add(mappingJackson2HttpMessageConverter);

final RestTemplate restTemplate = new RestTemplate(messageConverters);

// Use it as you consider appropriate

String uri = "https://clientapi.benchmarkemail.com/Contact/";

HttpEntity<String> request = new HttpEntity<String>(createHeaders());
ResponseEntity<Contact> response = restTemplate.exchange(uri, HttpMethod.GET, request, Contact.class);

Contact contact = response.getBody();

//...

Of course, you can reuse this configuration if needed by configuring a FactoryBean for RestTemplate and inject later in your controller, for instance.

Upvotes: 2

Related Questions