34

I'm trying to get the data from one app to another via RESTful WS and it works, but I cannot use this data since I cannot cast it... WS returns a List of objects like this:

{id=1, forename=John, surname=Bloggs, username=jbloggs, role=Graduate Developer, office=London, skills=[{technology=Java, experience=2.5}, {technology=Web, experience=2.0}, {technology=iOS, experience=0.0}, {technology=.NET, experience=0.0}]}

to get I it use Jackson's ObjectMapper:

ObjectMapper mapper = new ObjectMapper();

    List<ConsultantDto> list = new ArrayList<ConsultantDto>();


    try {

        list = mapper.readValue(con.getInputStream(), ArrayList.class);

    } catch (JsonGenerationException e) {

        e.printStackTrace();

    } catch (JsonMappingException e) {

        e.printStackTrace();

    } catch (IOException e) {

        e.printStackTrace();

    }

after that I have 3 lines of code:

System.out.println(list.get(0));
System.out.println(list.get(0).getForename());
return list;

return because this method's return value is passed to other webservice which displays correct data in a browser. Interesting thing happens with two printing lines, one prints the data from the top of this post ({id:1 ... }) but the other one throws exception:

java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.xxx.xxx.web.dto.rp.ConsultantDto

ConsultantDto and SkillDto are two legit classes which have all properties set to match the data from WS, all getters/setters are in place. As far as I'm concerned LinkedHashMap stores stuff as key:value pairs, so I just don't see where is this exception coming from. How can I fix it and why doesn't ObjectMapper just parse the value correctly (which it does when I get a single ConsultantDto rather than a List)?

3 Answers 3

50

You need to do this:

List<ConsultantDto> myObjects =
    mapper.readValue(jsonInput, new TypeReference<List<ConsultantDto>>(){});

(From this SO answer)

The reason you have to use TypeReference is because of an unfortunate quirk of Java. If Java had a proper generics, I bet your syntax would have worked.

Sign up to request clarification or add additional context in comments.

3 Comments

@Enno Is the same thing possible with a generic T type instead ?
With a generic type T use: Class<T> type; List<T> myObjects = mapper.readValue(jsonInput, mapper.getTypeFactory().constructCollectionType(ArrayList.class, type));
for generic type: List<T> where T is MyObjectClass.... ''' List<MyObjectClass> myObjects = mapper.readValue(json,mapper.getTypeFactory().constructCollectionType(ArrayList.class, MyObjectClass.class)); '''
7

import:

import com.fasterxml.jackson.databind.ObjectMapper;

object:

private ObjectMapper mapper = new ObjectMapper();

examples:

PremierDriverInfoVariationDTO premierDriverInfoDTO = 
mapper.convertValue(json, PremierDriverInfoVariationDTO.class); 
log.debug("premierDriverInfoDTO : {}", premierDriverInfoDTO);

or

Map<String, Boolean> map = mapper.convertValue(json, Map.class);
log.debug("result : {}", map);
assertFalse(map.get("eligiblePDJob"));

Comments

0

In my use case, I had 10 different APIs. All my API Responses were in the same json format but the content was different in one of the tags - /result/data

{
    "success": true,
    "result": {
        "type": "transactions",
        "data": {}
    },
    "error": [],
    "metadata": {
        "version": "v1",
        "code": 200,
        "desc": "OK",
        "trackingId": "TRACKINGID-1588097123800-1234567890",
        "generatedTimestamp": "2020-07-14T09:41:06.198Z"
    },
    "link": {
        "self": "/v1/myapp/customer/enquiry"
    }
}

In my spring boot code, I used the following generic method for all my API calls -

<T, R> ApiResponse<R> execute(URI uri, T entity, Class<R> clazz) {
    HttpEntity<T> httpEntity = new HttpEntity<>(entity);
    ApiResponse<R> body = this.restTemplate.exchange(uri, HttpMethod.POST, httpEntity,
            new ParameterizedTypeReference<ApiResponse<R>>() {
            }).getBody();
    return body;
}

It gave me the same error as you reported above. Based on this and some other SO answers, I tried the below as well and it did not work -

<T, R> ApiResponse<R> execute(URI uri, T entity, Class<R> clazz) {
    HttpEntity<T> httpEntity = new HttpEntity<>(entity); 
    ResponseEntity<String> response = this.scVaultCoreApi.exchange(uri, HttpMethod.POST, httpEntity, String.class);
    ObjectMapper mapper = new ObjectMapper();
    try {
        return mapper.readValue(response.getBody(), new TypeReference<ApiResponse<R>>() {
        });
    } catch (Exception e) {
        throw new RuntimeException();
    }
}

The other solution that worked was manually parsing the response stream using the ObjectMapper in jackson. But I couldn't do that for so many APIs I was using and the above error came in some APIs calls only. So, only for those APIs, instead of relying on the TypeReference conversion to the Class, I did not change the above methods that I defined, but I extracted the ApiResponse<T> as ApiResponse<Object> and parsed it as a LinkedHashMap only and created my particular class object manually.

ApiResult<MyResponse> res = execute(uri, payload, MyResponse.class).getResult();
Map<String, Map<String, Object>> map = (Map<String, Map<String, Object>>) res.getData();
MyResponse myResponse = MyResponse.builder()
    .accountBalance(new BigDecimal(map.get("key").get("balance").toString()))
    .clientName((String) map.get("key").get("clientName"))
    .build();
    

Best thing about this solution was that I did not have to change the base class execute method and the other API calls were working fine and only for the troubled API, I wrote the manual object creation code.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.