REST with Spring and Jackson full data binding

32,159

Solution 1

I suspect problem is due to type erasure, i.e. instead of passing generic parameter type, maybe only actions.getClass() is passed; and this would give type equivalent of List< ?>.

If this is true, one possibility would be to use an intermediate sub-class, like:

public class ActionImplList extends ArrayList<ActionImpl> { }

because this will the retain type information even if only class is passed. So then:

public @ResponseBody String executeActions(@RequestBody ActionImplList actions)

would do the trick. Not optimal but should work.

I hope someone with more Spring MVC knowledge can shed light on why parameter type is not being passed (perhaps it's a bug?), but at least there is a work around.

Solution 2

I have found that you can also work around the type erasure issue by using an array as the @RequestBody instead of a collection. For example, the following would work:

public @ResponseBody String executeActions(@RequestBody ActionImpl[] actions) { //... }

Solution 3

For your information, the feature will be available in Spring 3.2 (see https://jira.springsource.org/browse/SPR-9570)

I just tested it on current M2 and it works like a charm out of the box (no need to provide additionnal annotation to provide the parameterized type, it will be automatically resolved by new MessageConverter)

Solution 4

This question is already old, but I think I can contribute a bit anyway.

Like StaxMan pointed out, this is due to type erasure. It definitely should be possible, because you can get the generic arguments via reflection from the method definition. However, the problem is the API of the HttpMessageConverter:

T read(Class<? extends T> clazz, HttpInputMessage inputMessage);

Here, only List.class will be passed to the method. So, as you can see, it is impossible to implement a HttpMessageConverter that calculates the real type by looking at the method parameter type, as that is not available.

Nevertheless, it is possible to code your own workaround - you just won't be using HttpMessageConverter. Spring MVC allows you to write your own WebArgumentResolver that kicks in before the standard resolution methods. You can for example use your own custom annotation (@JsonRequestBody?) that directly uses an ObjectMapper to parse your value. You will be able to provide the parameter type from the method:

final Type parameterType= method.getParameterTypes()[index];
List<ActionImpl> result = mapper.readValue(src, new TypeReference<Object>>() {
    @Override
    public Type getType() {
        return parameterType;
    }
});

Not really the way TypeReference was intended to be used I presume, but ObjectMapper doesn't provide a more suitable method.

Share:
32,159
Javier Ferrero
Author by

Javier Ferrero

http://careers.stackoverflow.com/javierferrero

Updated on September 26, 2020

Comments

  • Javier Ferrero
    Javier Ferrero over 3 years

    I'm using Spring MVC to handle JSON POST requests. Underneath the covers I'm using the MappingJacksonHttpMessageConverter built on the Jackson JSON processor and enabled when you use the mvc:annotation-driven.

    One of my services receives a list of actions:

    @RequestMapping(value="/executeActions", method=RequestMethod.POST)
        public @ResponseBody String executeActions(@RequestBody List<ActionImpl> actions) {
            logger.info("executeActions");
            return "ACK";
        }
    

    I have found that Jackson maps the requestBody to a List of java.util.LinkedHashMap items (simple data binding). Instead, I would like the request to be bound to a List of typed objects (in this case "ActionImpl").

    I know this is easy to do if you use Jackson's ObjectMapper directly:

    List<ActionImpl> result = mapper.readValue(src, new TypeReference<List<ActionImpl>>() { }); 
    

    but I was wondering what's the best way to achieve this when using Spring MVC and MappingJacksonHttpMessageConverter. Any hints?

    Thanks

  • Javier Ferrero
    Javier Ferrero over 13 years
    Nice point, but it doesn't work. Jackson tryes to deserialize the received List as an instance of TypeReference and this produces a JsonMappingException.
  • sunny
    sunny almost 12 years
    I was stuck on a similar problem, tried this and it works. However by updating to 3.1.1.Release even List<ActionImpl> would work ...
  • sunny
    sunny almost 12 years
    The behaviour is arbitrary .. still confused
  • Martin
    Martin about 10 years
    This problem occurred in Spring 3.1 but was fixed in Spring 3.2: jira.spring.io/browse/SPR-9570