Mapping RestTemplate response to java Object
Asked Answered
A

2

6

I am using RestTemplate get data from remote rest service and my code is like this.

ResponseEntity<List<MyObject >> responseEntity = restTemplate.exchange(request, responseType);

But rest service will return just text message saying no record found if there are no results and my above line of code will throw exception. I could map result first to string and later use Jackson 2 ObjectMapper to map to MyObject.

ResponseEntity<String> responseEntity = restTemplate.exchange(request, responseType);
String jsonInput= response.getBody();
List<MyObject> myObjects = objectMapper.readValue(jsonInput, new TypeReference<List<MyObject>>(){});

But I don't like this approach. Is there any better solution for this.

Avicenna answered 28/2, 2019 at 18:59 Comment(3)
What about setting up a custom error handler? spring-resttemplate-overriding-responseerrorhandlerKed
i tried that approach as well but was not able to solve itAvicenna
If the whole API you are querying against is handling their responses like that you could also try to create and add a custom AbstractHttpMessageConverter as shown here resttemplate-jackson-custom-json-deserializing. But instead checking for an empty String check for your "text message saying no record found" text.Ked
D
3

What I usually do in my projects with restTemplate is save the response in a java.util.Map and create a method that converts that Map in the object I want. Maybe saving the response in an abstract object like Map helps you with that exception problem.

For example, I make the request like this:

List<Map> list = null;
List<MyObject> listObjects = new ArrayList<MyObject>();
HttpHeaders headers = new HttpHeaders();
HttpEntity<String> entity = new HttpEntity<>(headers);

ResponseEntity<Map> response = restTemplate.exchange(url, HttpMethod.GET, entity, Map.class);

if (response != null && response.getStatusCode().value() == 200) {
    list = (List<Map>) response.getBody().get("items"); // this depends on the response
    for (Map item : list) { // we iterate for each one of the items of the list transforming it
        MyObject myObject = transform(item);
        listObjects.add(myObject);
    }
}

The function transform() is a custom method made by me: MyObject transform(Map item); that receives a Map object and returns the object I want. You can check if there was no records found first instead of calling the method transform.

Desiree answered 14/3, 2019 at 9:6 Comment(0)
K
2

First of all you could write a wrapper for the whole API. Annotate it with @Component and you can use it wherever you want though Springs DI. Have a look at this example project which shows of generated code for a resttemplate client by using swagger codegen.

As you said you tried implementing a custom responserrorhandler without success I assume that the API returns the response body "no record found" while the status code is 200.

Therefore you could create a custom AbstractHttpMessageConverter as mentioned in my second answer. Because you are using springs resttemplate which is using the objectmapper with jackson we don't event have to use this very general super class to create our own. We can use and extend the more suited AbstractJackson2HttpMessageConverter class. An implementation for your specific use case could look as follows:

import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.stream.Collectors;

public class WeirdAPIJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter {
    public static final String NO_RECORD_FOUND = "no record found";

    public WeirdAPIJackson2HttpMessageConverter() {
        // Create another constructor if you want to pass an already existing ObjectMapper
        // Currently this HttpMessageConverter is applied for every MediaType, this is application-dependent
        super(new ObjectMapper(), MediaType.ALL);
    }

    @Override
    public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        try (BufferedReader br = new BufferedReader(new InputStreamReader(inputMessage.getBody(), DEFAULT_CHARSET))) {
            String responseBodyStr = br.lines().collect(Collectors.joining(System.lineSeparator()));
            if (NO_RECORD_FOUND.equals(responseBodyStr)) {
                JavaType javaType = super.getJavaType(type, contextClass);
                if(Collection.class.isAssignableFrom(javaType.getRawClass())){
                    return Collections.emptyList();
                } else if( Map.class.isAssignableFrom(javaType.getRawClass())){
                   return Collections.emptyMap();
                }
                return null;
            }
        }
        return super.read(type, contextClass, inputMessage);
    }
}

The custom HttpMessageConverter is checking the response body for your specific "no record found". If this is the case, we try to return a default value depending on the generic return type. Atm returning an empty list if the return type is a sub type of Collection, an empty set for Set and null for all other Class types.

Furthermore I created a RestClientTest using a MockRestServiceServer to demonstrate you how you can use your RestTemplate within the aforementioned API wrapper component and how to set it up to use our custom AbstractJackson2HttpMessageConverter.

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.client.RestClientTest;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.client.ExpectedCount;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.web.client.RestTemplate;

import java.util.List;
import java.util.Optional;

import static org.junit.Assert.*;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus;

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {RestTemplateResponseErrorHandlerIntegrationTest.MyObject.class})
@RestClientTest
public class RestTemplateResponseErrorHandlerIntegrationTest {

    static class MyObject {
        // This just refers to your MyObject class which you mentioned in your answer
    }

    private final static String REQUEST_API_URL = "/api/myobjects/";
    private final static String REQUEST_API_URL_SINGLE = "/api/myobjects/1";

    @Autowired
    private MockRestServiceServer server;

    @Autowired
    private RestTemplateBuilder builder;


    @Test
    public void test_custom_converter_on_weird_api_response_list() {
        assertNotNull(this.builder);
        assertNotNull(this.server);

        RestTemplate restTemplate = this.builder
                .messageConverters(new WeirdAPIJackson2HttpMessageConverter())
                .build();

        this.server.expect(ExpectedCount.once(), requestTo(REQUEST_API_URL))
                .andExpect(method(HttpMethod.GET))
                .andRespond(withStatus(HttpStatus.OK).body(WeirdAPIJackson2HttpMessageConverter.NO_RECORD_FOUND));

        this.server.expect(ExpectedCount.once(), requestTo(REQUEST_API_URL_SINGLE))
                .andExpect(method(HttpMethod.GET))
                .andRespond(withStatus(HttpStatus.OK).body(WeirdAPIJackson2HttpMessageConverter.NO_RECORD_FOUND));


        ResponseEntity<List<MyObject>> response = restTemplate.exchange(REQUEST_API_URL,
                HttpMethod.GET,
                null,
                new ParameterizedTypeReference<List<MyObject>>() {
                });

        assertNotNull(response.getBody());
        assertTrue(response.getBody().isEmpty());

        Optional<MyObject> myObject = Optional.ofNullable(restTemplate.getForObject(REQUEST_API_URL_SINGLE, MyObject.class));
        assertFalse(myObject.isPresent());

        this.server.verify();
    }
}
Ked answered 14/3, 2019 at 1:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.