2

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!

ustad
  • 459
  • 1
  • 6
  • 21
  • json 1st letter should always be in lowercase. and pojo field name's first letter should be in lowercase too. – priyranjan Dec 05 '20 at 00:43
  • Hi @priyranjan yes I've maintained the Java convention for my pojo. But I have no control over the json payload as its first letter is capitalized. This is why I've added the jackson property: spring.jackson.mapper.accept-case-insensitive-properties=true But its not being picked up.. – ustad Dec 05 '20 at 20:13
  • You are creating a totally new `RestTEmplate` which doesn't use auto-config of the `ObjectMapper`. Instead inject a `RestTEmplate` by creating an `@Bean` method that takes a `RestTemplateBuilder` to construct the rest template which does use the pre-configured object mapper. – M. Deinum Dec 07 '20 at 18:36
  • Thanks @M.Deinum! – ustad Dec 07 '20 at 19:54

2 Answers2

4

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.

s7vr
  • 73,656
  • 11
  • 106
  • 127
  • Awesome thank you so much @s7vr. Should have occurred to me that creating a new instance instead of injection ignored my configurations. – ustad Dec 07 '20 at 19:59
2

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.

jccampanero
  • 50,989
  • 3
  • 20
  • 49
  • Thanks so much @jccampanero. I used s7vr suggestion and it picked up my configuration properties. It should have occurred to me that creating a new instance instead of injecting it would ignore my custom properties. One additional question please: how do can I Iog jackson exceptions? if I comment out the ignore case property, it returns empty but does not give me any error feedback. Thank you so much again! Also, is it possible to accept your answer as well? That way both of you get the bounty? – ustad Dec 07 '20 at 19:57
  • 1
    You can place a new bounty once this finish, but please, it is unnecessary. It is fine, it is a great answer and solution. Well, the problem is that from the Jackson point of view in many cases, and depending on the provided configuration, there is no error, it just could not find a property, ... When there is an error, it is also difficult to deal with. Please, consider for instance for following possible approach: https://stackoverflow.com/questions/51519381/catching-handling-jackson-exceptions-with-a-custom-message. Please, see also this article: https://www.baeldung.com/jackson-exception – jccampanero Dec 07 '20 at 22:19
  • 1
    Please, do not hesitate to contact me if you need anything else. – jccampanero Dec 07 '20 at 22:44
  • It really helped me with my change. Thanks a ton – Shivam Gupta May 30 '23 at 05:14
  • You are welcome @ShivamGupta. I am very happy to hear that the answer was helpful. – jccampanero May 31 '23 at 21:54