javaspring-bootcastingjacksonobjectmapper

Conversion of JSON to Object


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)

screenshot of warning

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.

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

  1. I tried using 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)

screenshot of error

  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]

Solution

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