34

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.

Programmer Bruce
  • 64,977
  • 7
  • 99
  • 97
David Parks
  • 30,789
  • 47
  • 185
  • 328

4 Answers4

40

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!

oehmiche
  • 1,008
  • 1
  • 10
  • 9
  • I do not understand/follow how you are using this to return JSON objects. Isn't that putting the oness on the the client to ensure that it is calling the server with the specific type request? – Eric B. Sep 26 '13 at 19:24
  • thanks it helps.. after u have the body u need to typecast it to the desired type.. for teh example above it should be ((List)response.getBody()) – Anoop Isaac Apr 28 '14 at 21:30
  • @AnoopIsaac That may depend on which version of Spring you're using. In Spring 4.0 there's no need to typecast the body; it's already type `List`, which is the whole point of parameterizing `response` in the first place. I'm betting that's probably true in Spring 3.2 as well. – nbrooks Nov 06 '14 at 19:39
24

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.

StaxMan
  • 113,358
  • 34
  • 211
  • 239
10

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);
   }
}
Sean Patrick Floyd
  • 292,901
  • 67
  • 465
  • 588
  • Correct me if I misunderstand the problem here, but passing a Class to getJavaType necessitates type erasure (you can't pass List.class), you can only pass List.class, meaning you have no way of knowing what type of List to create (so you end up only being able to create generic JSON objects, not your desired Pojo objects within the List. I may be off base here, or need the issue re-framed to understand it. But, take a moment to consider this and let me know what you think. My understanding so far: the whole HttpMessageConverter interface limits your ability to create typed collections. – David Parks May 30 '11 at 09:23
  • @David no, you understand correctly, so your subclass would have to somehow "know" what `Mybean.class` is. (This could be implemented through Naming conventions, custom annotations etc.) – Sean Patrick Floyd May 30 '11 at 09:28
0

I have solved this problem by using the following configuration:

private static final String POJO_ARRAY_LIST = PojoArrayList.class.getCanonicalName();

@Bean
public HttpMessageConverter<Object> httpMessageConverter() {
    HttpMessageConverter<Object> httpMessageConverter = new MappingJackson2HttpMessageConverter() {
        @Override
        protected JavaType getJavaType(Type type, @Nullable Class<?> contextClass) {
            JavaType javaType;
            if (type != null && POJO_ARRAY_LIST.equals(type.getTypeName())) {
                ObjectMapper objectMapper = new ObjectMapper();
                TypeFactory typeFactory = objectMapper.getTypeFactory();
                CollectionType collectionType = typeFactory.constructCollectionType(ArrayList.class, Pojo.class);
                javaType = collectionType;
            } else {
                javaType = super.getJavaType(type, contextClass);
            }
            return javaType;
        }
    };
    return httpMessageConverter;
}

where PojoArrayList is a final class that extends ArrayList<Pojo>.

Aliuk
  • 1,249
  • 2
  • 17
  • 32