I am working on a Microservices Project in Java, using Spring Boot and Eureka. I came across a scenario where I fetch the data from another microservice using:
List<Rating> ratings=(List<Rating>) restTemplate.getForObject("http://localhost:8083/microservices/rating/get-all-ratings-by-user?userId=ddb8e2a9-ac6f-460d-a43e-eae23d18450c", Map.class).get("data");
I get a warning on this line:
<Map> Map org.springframework.web.client.RestTemplate.getForObject(String url, Class<Map> responseType, Object... uriVariables) throws RestClientException
Type safety: Unchecked cast from Object to List<Rating> Java(16777761)
Potential null pointer access: The method getForObject(String, Class, Object[]) may return null Java(536871831)
Explanation
I get the following response from the URL I used above:
{
"data": [
{
"ratingId": "6140b240-9a97-430d-92b9-0fcfa8edc96f",
"userId": "ddb8e2a9-ac6f-460d-a43e-eae23d18450c",
"hotelId": "1093aa3f-8529-4330-8ce8-caa82546200b",
"rating": 4,
"feedback": "Died peacefully"
}
],
"message": "Success",
"code": "2000"
}
Aim
I want to extract the list of Rating objects from the response's data field and store it as a List<Rating>. Further, I want to iterate over it and perform other operations.
Map (Map.class passed in getForObject() method).Map using .get("data").List<Rating>.My Question
Using this above approach I am able to obtain a list of objects of Rating class. But, can somebody explain how this map (obtained using .get("data")) automatically gets converted to List<Rating> using simple type conversion? The code doesn't seem to use any Object Mapper like Jackson. Also, I am getting a warning. Is there any way to remove it? I can send the List<Rating> as it is to a GET request. But if I try to use a method on the List, I get errors, see below
Follow Up
stream() and map() on the List<Rating>, I obtained above:ratings=ratings.stream().map(rating->{
// api call to hotel service to obtain the hotel
Hotel hotel=(Hotel) restTemplate.getForEntity("http://localhost:8086/microservices/hotel/get-hotel-by-id?hotelId="+rating.getHotelId(), Map.class).getBody().get("data");
logger.info("fetched hotel: ", hotel);
rating.setHotel(hotel);
}).collect(Collectors.toList());
But I get a compile-time error on the .map():
<R> Stream<R> java.util.stream.Stream.map(Function<? super Rating, ? extends R> arg0)
The method map(Function<? super Rating,? extends R>) in the type Stream<Rating> is not applicable for the arguments ((<no type> rating) -> {}) Java(67108979)
forEach() gives class cast exception:ratings.forEach((rating)->{
// api call to hotel service to obtain the hotel
Hotel hotel=(Hotel) restTemplate.getForEntity("http://localhost:8086/microservices/hotel/get-hotel-by-id?hotelId="+rating.getHotelId(), Map.class).getBody().get("data");
logger.info("fetched hotel: ", hotel);
rating.setHotel(hotel);
});
Error on forEach():
java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to class com.example.user_service.entities.Rating (java.util.LinkedHashMap is in module java.base of loader 'bootstrap'; com.example.user_service.entities.Rating is in unnamed module of loader org.springframework.boot.devtools.restart.classloader.RestartClassLoader @db2af5f)
at java.base/java.util.ArrayList.forEach(Unknown Source) ~[na:na]
at com.example.user_service.ServiceImpl.UserServiceImpl.getUser(UserServiceImpl.java:84) ~[classes/:na]
at com.example.user_service.controller.UserController.getUserById(UserController.java:53) ~[classes/:na]
You are overcomplicating the task, you can convert the json response from your RestTemplate to a String value, then extract the data part inside of it with jackson library like below:
JsonNode root =mapper.readTree(json); //<--convert the json string to a JsonNode
JsonNode data = root.at("/data"); //<-- selecting the "data" part
//conversion to List<Rating> avoid problems due to list type erasure
//with the help of jackson TypeReference class
List<Rating> ratings = mapper.convertValue(data, new TypeReference<List<Rating>>() {});
This is achieved using the JsonNode#at method that locates the specific node with data label inside your json, to convert it to a List<Rating> it is necessary to use TypeReference to instantiate reference to generic type List<Rating>.