Search code examples
javaspringresttemplate

Using RestTemplate.exchange with an inconsistent API response


By inconsistent I mean that variable types can differ depending on the API response. So a named variable could be an Object, a List of Objects, or bizarrely even a String. I do not and cannot control the third-party API I'm consuming.

I'm using restTemplate.exchange(String url, HttpMethod method, HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables), and the top-level responseType is consistent. It's in the child (and descendant) objects where the types may differ.

Am I stuck with pivoting to consuming the API response as a String, and do manual parsing? Or is there a way to handle the fact that the variable types might map differently (similar to how GSON supports custom serialisation / deserialisation).


Solution

  • Managed to find a way through this. I did have to read the API response as a String and take it from there. General steps:

    1. restTemplate.exchange into String response body
    2. Setup an ObjectMapper
    3. Configure that to accept single values as arrays, and empty strings as null objects
    4. Read into the POJO of your choice

    Now, this won't be ideal for everyone - in a perfect world you shouldn't have to relax the JSON parsing rules at all. This is all because I'm handling a very inconsistent API.

    Rough code example is as-follows (as our internal stack is pretty complex, so I've had to drag bits out from classes here and there):

    String exampleEndpoint = Constants.EXAMPLE_ENDPOINT;    
    ResponseEntity<String> responseEntity = restTemplate.exchange(uri.toString(), HttpMethod.GET, null, String.class);
    String stringResponse = responseEntity.getBody();
    
    ExamplePOJO examplePojo = null;
    
    ObjectMapper mapper = new ObjectMapper();
    mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
    mapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
    
    try {
        examplePojo = mapper.readValue(stringResponse, ExamplePOJO.class);
    } catch (JsonProcessingException | NullPointerException ne) {
        // JsonProcessingException is from readValue, NPE is to catch the string response
        // being null in the event you don't want to let it bubble up further
        logger.error(ne.getLocalizedMessage());
    }