Spring MVC REST using @RequestBody List<?> r

2019-05-10 03:10发布

问题:

I am using Spring 4 + Jackson 2 and have written a fully functional POST method using @RequestBody on a custom class. This method has no trouble unmarshalling the object.

@ResponseBody
@RequestMapping(value="store", method = RequestMethod.POST)
public ServiceResponse store(@RequestBody CustomClass list) {
    ...
}

// Request: { code: "A", amount: 200 }

When I attempted to add another method to handle a collection of the same class instead, my POST requests were returning with the following error.

HTTP Status 400: The request sent by the client was syntactically incorrect.

I note that this error typically occurs when the JSON submitted does not match the entity class. However, all I am doing is submitting an array of the same object instead of the object itself, which has already proven to work.

@ResponseBody
@RequestMapping(value="store-bulk", method = RequestMethod.POST)
public ServiceResponse storeBulk(@RequestBody List<CustomClass> list) {
    ...
}

// Request: [{ code: "A", amount: 200 }, { code: "B", amount: 400 }]

Am I missing something here?

回答1:

In Java, type information for generics is erased at runtime, so Spring sees your List<CustomClass> object as List<Object> object, thus it cannot understand how to parse it.

One of ways to solve it, you could capture the type information by creating a wrapper class for your list, like this:

public class CustomClassList extends ArrayList<CustomClass> {
}


回答2:

Sergey is right that the issue is due to type erasure. Your easiest way out is to bind to an array, so

@ResponseBody
@RequestMapping(value="store-bulk", method = RequestMethod.POST)
public ServiceResponse storeBulk(@RequestBody CustomClass[] object) {
    ...
}


回答3:

The answer is that Spring 4 doesn't actually get rid of type erasure, contrary to what some other solutions suggest. While experimenting on debugging via manual unmarshalling, I decided to just handle that step myself instead of an implicit cast that I have no control over. I do hope someone comes along and proves me wrong, demonstrating a more intuitive solution though.

@ResponseBody
@RequestMapping(value="store-bulk", method = RequestMethod.POST)
public ServiceResponse storeBulk(@RequestBody String json) {
    try {
        List<CustomClass> list = new ObjectMapper().readValue(json, new TypeReference<List<CustomClass>>() { });
        ...
    } catch (Exception e) {
        ...
    }
}

Bonus: Right after I got this working, I bumped into this exception:

IllegalStateException: Already had POJO for id

If anyone gets this, it's because the objects in the list happen to reference some object that another item in the list already references. I could work around this since that object was identical for my entire collection, so I just removed the reference from the JSON side from all but the first object. I then added the missing references back after the JSON was unmarshalled into the List object.

Two-liner for the Java 8 users (the User object reference was the issue in my case):

User user = list.get(0).getUser();
list.stream().filter(c -> c.getUser() == null).forEach(t -> t.setUser(user));