2

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-
enter image description here

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. Further, I want to iterate over it and perform other operations.

  1. I convert it to a Map (Map.class passed in getForObject() method).
  2. I get the list of type Map using .get("data").
  3. This is type casted to 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 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 as it is to a GET request.
    But If I try to use a method on the List I get errors-
    Follow Up-
  4. I tried using stream() and map() on the List I obtained above, but got an error-
 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());

Got a compile-time error on the .map()-

enter image description here

  1. 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]
Arvind Kumar Avinash
  • 71,965
  • 6
  • 74
  • 110
ayush
  • 464
  • 5
  • 17
  • 2
    Does it work? I'd expect `get("data")` to return a `List>`, not a `List`. – knittl Dec 26 '22 at 20:04
  • Yes, its working but with a warning as I stated above. I also expect the same, that's the reason I posted this question to have a better understanding. – ayush Dec 26 '22 at 20:19
  • 1
    When I say "working", I meant "can you execute it without (class cast) exceptions"? It's compiling fine, but will it work at runtime? – knittl Dec 26 '22 at 20:37
  • Please check the question, I have elaborated the problem a bit more for better understanding. – ayush Dec 27 '22 at 06:33
  • coder001 the class cast exception you encounter with forEach is exactly what I wrote in my first two comments: "cannot cast LinkedHashMap to Rating" (because `get("data")` returns a `List>`, not a `List`. So the answer to your question "how is it possible that it is converted to a ratings list?" is "it's not possible and that's not what's happening; nothing is converted, you only have maps and lists of maps" – knittl Dec 27 '22 at 11:01

1 Answers1

2

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>.

dariosicily
  • 4,239
  • 2
  • 11
  • 17