Reputation: 3281
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)?
Upvotes: 34
Views: 107285
Reputation: 387
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.
Upvotes: 0
Reputation: 371
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"));
Upvotes: 7
Reputation: 26882
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.
Upvotes: 50