Spring Generic REST Controller: Parsing Request Body

I have a controller:

@RestController @RequestMapping(value = "/{entity}", produces = MediaType.APPLICATION_JSON_VALUE) public class CrudController<T extends SomeSuperEntity> { @RequestMapping(method = GET) public Iterable<T> findAll(@PathVariable String entity) { } @RequestMapping(value = "{id}", method = GET) public T findOne(@PathVariable String entity, @PathVariable String id) { } @RequestMapping(method = POST) public void save(@PathVariable String entity, @RequestBody T body) { } } 

SomeSuperEntity class is as follows:

 public abstract class SomeSuperEntity extends AbstractEntity { // some logic } 

And AbstractEntity its abstract class with some field:

 public abstract class AbstractEntity implements Comparable<AbstractEntity>, Serializable { private Timestamp firstField; private String secondField; public Timestamp getFirstField() { return firstField; } public void setFirstField(Timestamp firstField) { this.firstField = firstField; } public String getSecondField() { return secondField; } public void setSecondField(String secondField) { this.secondField = secondField; } } 

All subclasses of SomeSuperEntity are simple JavaBeans. In the case of the findAll() and findOne(id) methods, everything works fine. I create an object at the service level and return it to the client as JSON with all the fields specified in the subclass and in AbstractEntity .

But when I tried to get the request body in save(entity, body) , I got the following error:

Failed to read the document: it is not possible to instantiate SomeSuperEntity, problem: abstract types either have to be mapped to specific types, have a custom deserializer, or create additional type information

If I remove the abstraction from SomeSuperEntity , everything works, but I request the body, I received only those fields that were declared in AbstractEntity .

And here is my question: are there any ways to solve such problems in my case? If not, what is the best solution here without any structural changes (which makes subcontloller not an option for each object)? Is getting the body plain text would be a good idea? Or would it be better to use Map for this?

I am using Spring v4.2.1 and Jackson 2.6.3 as a converter.

A bit of information about common controllers, but I could not find anything that would cover my case. Therefore, please proceed with a duplication of the question.

Thanks in advance.

UPD : it currently works as follows: I add an extra check to my MessageConverter and define @RequestBody as a String

 @Override public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { if (IGenericController.class.isAssignableFrom(contextClass)) { return CharStreams.toString(new InputStreamReader(inputMessage.getBody(), getCharset(inputMessage.getHeaders()))); } return super.read(type, contextClass, inputMessage); } 

Then, at the service level, I determine which object I received (in plain json) and converts it:

 final EntityMetaData entityMetadata = getEntityMetadataByName(alias); final T parsedEntity = getGlobalGson().fromJson(entity, entityMetadata.getEntityType()); 

Where EntityMetaData is an enum with certain relationships between the object alias and the class. The alias comes as @PathVariable .

+7
source share
2 answers

What Spring really sees:

 public class CrudController { @RequestMapping(method = GET) public Iterable<Object> findAll(@PathVariable String entity) { } @RequestMapping(value = "{id}", method = GET) public Object findOne(@PathVariable String entity, @PathVariable String id) { } @RequestMapping(method = POST) public void save(@PathVariable String entity, @RequestBody Object body) { } } 

It does not matter for the returned objects, since Jackson will generate proper JSON output anyway, but it looks like Spring cannot handle the incoming object in the same way.

You can try replacing generics only with the SomeSuperEntity function and take a look at Spring @RequestBody containing a list of different types (but the same interface)

0
source

For a long time, I found myself working in Jackson:

 @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class") public interface ApiRequest { } 

and use

 REQUEST extends ApiRequest 

do not modify MessageConverter. This way, additional class information in your json request will still be required. For example, you can do:

 public abstract class ApiPost<REQUEST extends ApiRequest > { abstract protected Response post(REQUEST request) throws ErrorException; @ResponseBody @RequestMapping(method = RequestMethod.POST) public Response post( @RequestBody REQUEST request ) throws IOException { return this.post(request); } } 

and then for the controller

  public class ExistApi { public final static String URL = "/user/exist"; @Getter @Setter public static class Request implements ApiRequest{ private String username; } } @Controller @RequestMapping(URL) public class ExistApiController extends ApiPost<Request> { @Override protected Response post(Request request) implements ApiRequest { //do something // and return response } } 

And then send the request as {"Username": xxx, "@class": "package ..... Request"}

refers to https://github.com/FasterXML/jackson-docs/wiki/JacksonPolymorphicDeserialization

But for me, the best solution is not to use spring to transform the message, and leave it an abstract class.

 public abstract class ApiPost<REQUEST> { @Autowired private ObjectMapper mapper; protected Class<REQUEST> getClazz() { return (Class<REQUEST>) GenericTypeResolver .resolveTypeArgument(getClass(), ApiPost.class); } abstract protected Response post(REQUEST request) throws ErrorException; @ResponseBody @RequestMapping(method = RequestMethod.POST) public Response post( @RequestBody REQUEST request ) throws IOException { //resolve spring generic problem REQUEST req = mapper.convertValue(request, getClazz()); return this.post(request); } } 

with this we do not require the ApiRequest and @class interface in the json request, we will separate the front and backend.

Sorry my bad english.

0
source

All Articles