Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring/json: Convert a typed collection like List<MyPojo>

I'm trying to marshal a list: List<Pojo> objects via the Spring Rest Template.

I can pass along simple Pojo objects, but I can't find any documentation that describes how to send a List<Pojo> objects.

Spring is using Jackson JSON to implement the HttpMessageConverter. The jackson documentation covers this:

In addition to binding to POJOs and "simple" types, there is one additional variant: that of binding to generic (typed) containers. This case requires special handling due to so-called Type Erasure (used by Java to implement generics in somewhat backwards compatible way), which prevents you from using something like Collection<String>.class (which does not compile).

So if you want to bind data into a Map<String,User> you will need to use:

Map<String,User> result = mapper.readValue(src, new TypeReference<Map<String,User>>() {});

where TypeReference is only needed to pass generic type definition (via anynomous inner class in this case): the important part is <Map<String,User>> which defines type to bind to.

Can this be accomplished in the Spring template? I took a glance at the code and it makes me thing not, but maybe I just don't know some trick.


Solution

The ultimate solution, thanks to the helpful answers below, was to not send a List, but rather send a single object which simply extends a List, such as: class PojoList extends ArrayList<Pojo>. Spring can successfully marshal this Object, and it accomplishes the same thing as sending a List<Pojo>, though it be a little less clean of a solution. I also posted a JIRA in spring for them to address this shortcoming in their HttpMessageConverter interface.

like image 520
David Parks Avatar asked May 30 '11 06:05

David Parks


3 Answers

In Spring 3.2 there is now support for generic types using the new exchange()-methods on the RestTemplate:

 ParameterizedTypeReference<List<MyBean>> typeRef = new ParameterizedTypeReference<List<MyBean>>() {};
 ResponseEntity<List<MyBean>> response = template.exchange("http://example.com", HttpMethod.GET, null, typeRef);

Works like a charm!

like image 56
oehmiche Avatar answered Oct 20 '22 04:10

oehmiche


One way to ensure that generic type parameters are included is to actually sub-class List or Map type, such that you have something like:

static class MyStringList extends ArrayList<String> { }

and return instance of that list.

So why does this make a difference? Because generic type information is retained in just a couple of places: method and field declarations, and super type declarations. So whereas "raw" List does NOT include any runtime type information, class definition of "MyStringList" does, through its supertype declarations. Note that assignments to seemingly typed variables do not help: it just creates more compile-time syntactic sugar: real type information is only passed with Class instances (or lib-provided extensions thereof, like JavaType and TypeReference in Jackson's case).

Other than this, you would need to figure out how to pass Jackson either JavaType or TypeReference to accompany value.

like image 24
StaxMan Avatar answered Oct 20 '22 04:10

StaxMan


If I read the docs for MappingJacksonHttpMessageConverter right, you will have to create and register a subclass of MappingJacksonHttpMessageConverter and override the getJavaType(Class<?>) method:

Returns the Jackson JavaType for the specific class. Default implementation returns TypeFactory.type(java.lang.reflect.Type), but this can be overridden in subclasses, to allow for custom generic collection handling. For instance:

protected JavaType getJavaType(Class<?> clazz) {
   if (List.class.isAssignableFrom(clazz)) {
     return TypeFactory.collectionType(ArrayList.class, MyBean.class);
   } else {
     return super.getJavaType(clazz);
   }
}
like image 10
Sean Patrick Floyd Avatar answered Oct 20 '22 02:10

Sean Patrick Floyd