Resttemplate unable to parse array of type T - spring

I'm facing a problem with Spring and restTemplate. I want to send an object (ListResponse) that contains a generic array. The defenition is as follow:
public class ListResponse<T> implements Serializable {
private long total;
private int page;
private int pageSize;
private T[] objects;
I send a request whith restTemplate.getForObject(). As a result I get an object of type ListResponse but the objects array contains an array of LinkedHashMaps instead of an array with objects of type T.
It seems like restTemplate can not convert the elements in the array to their correct type.
How can I make sure that I get an array of objects of type T back ?

I had this problem today and here is the solution that I came up with (actually, that one of my co-workers suggested). We use it with an interface that returns List<MyDto>.
When you call the RestTemplate, don't pass in the generic type.
Define: public class MyDtoListTemplate extends ListTemplate<MyDto>
Then, call
MyDtoListTemplate template = restTemplate.getForObject("url", MyDtoListTemplate .class, mapOfPathVariables);
It's a bummer that you have to define a concrete class that extends/implements the generic type, but then the generic information is available to the jackson deserializer.

I remember I was able to deserialize generic classes with Jackson 2. I had to add MappingJackson2HttpMessageConverter converter to RestTemplate before making any Http calls with it.
RestTemplate template = new RestTemplate();
template.getMessageConverters().add(new MappingJackson2HttpMessageConverter());

Unfortunately there's no easy way of doing it that I know of. The problem is that the RestTemplate is told which object type to expect. As long as all the fields in this object has a corresponding element in the json/xml, everything works fine. In the case of generics, the serializer doesn't know which class to expect so it just turns the map it gets to a java Map.
You will have the same problem if you tried to getForObject for a generic return type.

Related

Given an assignment to return specific data using Springboot reactive but the JSON is really complicated

I am new to Springboot reactive
I was asked to call the following endpoint and return todays weather data only:
https://api.weather.gov/gridpoints/MLB/33,70/forecast
I believe I need to use something like this...
WebClient.create().get()
.uri("https://api.weather.gov/gridpoints/MLB/33,70/forecast")
.retrieve()
.bodyToMono(WeatherClass.class)
.block();
Do I need to map out an entire java object to match the JSON at the endpoint? is there an easy way to perhaps just grab the a certain piece of the JSON?
How would I handle something like the #context annotation in the JSON.
The WebClient in spring boot automatically uses Jackson's ObjectMapper to unmarshall json to a java object when the content type of the response is application/json. So there is no need to pull in any additional libraries or have to write any specific unmarshalling code, unless you want to use an alternate json-to-java library.
When using Jackson, you don't need to map every field in the json to your java object. You can annotate your java class with #JsonIgnoreProperties to inform jackson to ignore any properties that may appear in the json but do not have a matching field in your java object.
An example WeatherClass in which you want only the #context and forecastGenerator unmarshalled would look something like this
#JsonIgnoreProperties
public class WeatherClass {
private final List<Object> context;
private final WeatherProperties weatherProperties;
public WeatherClass(#JsonProperty("#context") List<Object> context,
#JsonProperty("properties") WeatherProperties weatherProperties) {
this.context = context;
this.weatherProperties = weatherProperties;
}
private class WeatherProperties {
private final String forecastGenerator;
private WeatherProperties(#JsonProperty("forecastGenerator") String forecastGenerator) {
this.forecastGenerator = forecastGenerator;
}
}
}
Note
#context seems to be an array that can contain multiple types (both objects and strings in your example). I've used Object to work around this but obviously isn't the most graceful solution but should be adequate to demonstrate how Jackson works
Alternatively, you can unmarshall the response to a JsonNode, which you can then use to traverse the structure of the json without converting it to a java object. For example
String forecastGenerator = WebClient.create().get()
.uri("https://api.weather.gov/gridpoints/MLB/33,70/forecast")
.retrieve()
.bodyToMono(JsonNode.class)
.block().get("properties").get("forecastGenerator").toString()
There are many other annotations provided by Jackson that can used to define how the unmarshaller functions. Too many to cover here. See Jackson Deserialisation Annotations

How can I properly override a method declared in an abstract generic restcontroller?

I'm having some trouble implementing a function over some pre-existing code.
Other programmers working on this project previously defined a genric abstract "restcontroller" (it's not actually annotated as #RestController but it's meant to be extended by classes with that annotation)
public abstract class AbstractController<T extends AbstractEntity, R extends JpaRepository<T, Integer>> {
#GetMapping(value = "/getall")
public Paging<T> getAll(#RequestParam Integer itemsPerPage,
#RequestParam Integer pageIndex,
#RequestParam Map<String, String> filters,
#Autowired Consumer consumer) {
//Fetch entities of type T from repository R and return them
}
//other generic crud operations
}
This class is usually extended by concrete controllers that simply define other operations on their specific types, but do no alter generic crud operations.
What I want to do is extend this class, but override the getAll method, like this:
#RestController
#RequestMapping("/api/tasks")
public class TaskController extends AbstractController<Task, TaskRepository> {
#Override
public Paging<Task> getAll(Integer itemsPerPage, Integer pageIndex, Map<String, String> filters, Consumer consumer) {
LoggerFactory.getLogger(LazyTaskController.class).log("function called successfully!");
Paging<Task> paging = super.getAll(itemsPerPage, pageIndex, filters, consumer);
//do things with return value before returning
return paging;
}
}
If I call BASEURL/api/tasks/getall?itemsPerPage=25&pageIndex=0 without overriding the getAll method, the parameters are wired correctly (the Map contains two values, itemsPerPage and pageIndex, as expected, and consumer contains a concrete implementation of the intercace Consumer).
However if I do override it, the Map for some reason contains two values, one with key "consumer" and type Proxy, and another with key "org.springframework.validation.BindingResult.consumer" and value of type BeanPropertyBindingResult; and consumer contains a Proxy.
I suppose the #Override interferes with the autowiring of Consumer, but I can't figure out how to properly achieve what I have in mind (manipulating the results of getAll before returning them).
Thank you in advance
Nevermind, I solved it.
The problem with the Map was solved by adding #RequestParam and #Autowired annotations to the overridden method parameters as well.
The problem with the Consumer concrete type was somehow solved by applying a custom annotation that I found on another class in the codebase, I'm still not sure about what that annotation does but at least I know what to look for now.

Jackson deserializer priority?

I have a Spring Boot app that is modeling ActityStreams objects and for the most part Jackson's Polymorphic Deserialization works well.
There are 'objects' in the JSON which are references (links) and not JSON objects with type information. For instance
"actor":"https://some.actors.href/ rather than
"actor":{
"type":"Actor",
"name":"SomeActor"
}
I've written custom deserializers and and placed them on the fields to deal with this
#JsonDeserialize (using = ActorOrLinkDeserializer.class)
private Actor actor;
However my ActorOrLinkDeserializer is instantiated but never called and Jackson complains with Missing type id when trying to resolve subtype of [simple type, class org.w3.activity.streams.Actor]: missing type id property 'type' (for POJO property 'actor') which is from the polymorphic deserializer.
It appears that the polymorphic deserialization code takes precedence over my local #JsonDeserialize annotation and I need a way to force my code to run first.
I've tried using my own ObjectMapper rather than Boot's and there's no difference.
I'd appreciate pointers and suggestions.
It turns-out there's a fairly simple solution to this problem using a DeserializationProblemHandler.
What I've implemented that works for all test cases so far is
1.
objectMapper.addHandler(new DeserProblemHandler());
or register with Spring Boot.
2.
public class DeserProblemHandler extends DeserializationProblemHandler {
public JavaType handleMissingTypeId(DeserializationContext ctxt, JavaType baseType, TypeIdResolver idResolver, String failureMsg) {
return TypeFactory.defaultInstance().constructType(baseType.getRawClass());
}
}
Add a constructor to each of the polymorphic classes that takes a string argument which is the href.

RestTemplate getForObject to POJO

I'm using RestTemplate.getForObject() to retrieve json that includes a couple of objects and arrays, I only want to convert one of the objects inside this json to a POJO, I don't care about the other objects inside this json.
What is the proper way to approach this?
Edit:
Another approach from accepted answer, we can use jacksons ObjectMapper
#Autowired
private ObjectMapper jacksonObjectMapper;
then
LinkedHashMap obj1 = restTemplate.getForObject(uri, LinkedHashMap.class, params);
LinkedHashMap obj2 = (LinkedHashMap)test.get("flightStatuses");
Flight flight = jacksonObjectMapper.convertValue(obj2, Flight.class);
You get the idea, just get a generic datatype from your json structure then use ObjectMapper to convert it to the class you need.
One solution would be to create a wrapper class, which includes the POJO you want to deserialize and ignore all other properties using #JsonIgnoreProperties. You would then retrieve the wrapper object and get the POJO from it.
#JsonIgnoreProperties(ignoreUnknown=true)
public class Wrapper {
private MyPojo myPojo;
}
MyPojo myPojo = restTemplate.getForObject("url", Wrapper.class).getMyPojo();

Mock controller with an object parameter

I'm trying to test a method with this signature:
#RequestMapping(value="/Employee/{id}", method=RequestMethod.PUT, consumes="application/json")
#Transactional
public #ResponseBody Map update(#PathVariable Integer id,
#RequestBody HashMap<String, Object> information) {
}
The problem is that MockMvc param attributes accept only String parameters, is there a way to pass a HashMap or an instance class object to the RequestBody as parameter?
When I try to pass a HashMap as a string, I get a MismatchException.
You need to use Jackson for this. The idea is to deserialize your objects (doesn't matter that it's HashMap) into JSON string and pass it into MockMvc.
Here is tutorial how to do that. Just search for TestClass there and take a look how it is used. Skip the unit testing of GET requests.

Resources