Spring MVC @RequestBody receive an Object wrapper with non-primitive attributes

51,534

Solution 1

I'm going to answer my own question. First of all special thanks to Sotirios Delimanolis because he gave me the key in order to investigate what it was happening.

As you know, I create the following json from the view:

{"manager":{"username":"admin","password":"admin"},"userToSubscribe":{"username":"newuser","password":"newpassword","email":"[email protected]"},"openid":"https://myopenid..."}

I changed it a little bit because I realised that is not necessary to create a object Subscription and a var Subscription. If you build the JSON like this, it will work perfectly:

var manager = {
    username: "admin",
    password: "admin"
};
var userToSubscribe = {
    username: "newuser",
    password: "newpassword",
    email: "[email protected]"
};

var openid = "https://myopenid...";

$.ajax({
    url: '/dp/rest/isUserSuscribed.json',
    type: 'POST',
    dataType: 'json',
    contentType: 'application/json',
    mimeType: 'application/json',
    data: JSON.stringify({manager : manager, userToSubscribe : userToSubscribe, openid : openid})   
});

The controller receives this json:

@RequestMapping(method=RequestMethod.POST, value="/isUserSuscribed.json")
public @ResponseBody ResponseMessageElement<Boolean> isUserSuscribed(@RequestBody SubscriptionWrapper subscription){

And the SubscriptionWrapper...

private static class SubscriptionWrapper {
    BasicUser manager;
    BasicUser userToSubscribe;
    String openid;

    public BasicUser getManager() {
        return manager;
    }
    public void setManager(BasicUser manager) {
        this.manager = manager;
    }
    public BasicUser getUserToSubscribe() {
        return userToSubscribe;
    }
    public void setUserToSubscribe(BasicUser userToSubscribe) {
        this.userToSubscribe = userToSubscribe;
    }
    public String getOpenid() {
        return openid;
    }
    public void setOpenid(String openid) {
        this.openid = openid;
    }
}

So... What is the problem? I was receiving an Incorrect Request 400 error... I debugged the MappingJackson2HttpMessageConverter as Sotirios suggested and there was an exception (No suitable constructor). Jackson Mapping is not able to build an inner class whether this class is not static. Setting SubscriptionWrapper to static was the solution to my problem.

You can also check these answers: http://stackoverflow.com/questions/8526333/jackson-error-no-suitable-constructor

http://stackoverflow.com/questions/12139380/how-to-convert-json-into-pojo-in-java-using-jackson

And if you have problems to deserialize, check this: http://stackoverflow.com/questions/17400850/is-jackson-really-unable-to-deserialize-json-into-a-generic-type

Thanks for all the replies.

Solution 2

Looking at your JSON

{
    "subscription": {
        "manager": {
            "username": "admin",
            "password": "admin"
        },
        "userToSubscribe": {
            "username": "newuser",
            "password": "newpassword",
            "email": "[email protected]"
        },
        "openid": "myopenid"
    }
}

The root element is subscription and it is a JSON object. Your Subscription class doesn't have a subscription field. So there is nothing to map the subscription element to and it therefore fails with a 400 Bad Request.

Create a class SubscriptionWrapper

public class SubscriptionWrapper {
    private Subscription subscription;

    public Subscription getSubscription() {
        return subscription;
    }

    public void setSubscription(Subscription subscription) {
        this.subscription = subscription;
    }
}

and change your handler method to accept an argument of this type

public @ResponseBody SimpleMessage subscribeUser(@RequestBody SubscriptionWrapper subscriptionWrapper)

You might need to configure the ObjectMapper in MappingJacksonHttpMessageConverter (FYI you should be using MappingJackso2nHttpMessageConverter), so that it ignores missing properties.

Solution 3

You don't need to do this by yourself. You need to add this dependency in your pom:

<dependencies>
    <dependency>
        <groupId>org.codehaus.jackson</groupId>
        <artifactId>jackson-mapper-asl</artifactId>
        <version>1.8.5</version>
    </dependency>
  </dependencies>

After that Spring will do conversion for you.

Share:
51,534
mannuk
Author by

mannuk

Updated on November 15, 2020

Comments

  • mannuk
    mannuk over 3 years

    I create the JSON as follows:

        var manager = {
            username: "admin",
            password: "admin"
        };
        var userToSubscribe = {
            username: "newuser",
            password: "newpassword",
            email: "[email protected]"
        };
    
        var openid = "myopenid";
    
        var subscription = {
                manager: manager,
                userToSubscribe : userToSubscribe,
                openid : openid
        };
    
        $.ajax({
            url: '/myapp/rest/subscribeUser.json',
            type: 'POST',
            dataType: 'json',
            contentType: 'application/json',
            mimeType: 'application/json',
            data: JSON.stringify({subscription : subscription})   
        });
    

    This is the JSON that is sent:

    {"subscription":{"manager":{"username":"admin","password":"admin"},"userToSubscribe":{"username":"newuser","password":"newpassword","email":"[email protected]"},"openid":"myopenid"}}  
    

    And I would like to map this JSON to a Wrapper Class. This is the wrapper:

    private class Subscription{
        private User manager;
        private User userToSubscribe;
        private String openid;
        public User getManager() {
            return manager;
        }
        public void setManager(User manager) {
            this.manager = manager;
        }
        public User getUserToSubscribe() {
            return userToSubscribe;
        }
        public void setUserToSubscribe(User userToSubscribe) {
            this.userToSubscribe = userToSubscribe;
        }
        public String getOpenid() {
            return openid;
        }
        public void setOpenid(String openid) {
            this.openid = openid;
        }
    }
    

    The jackson dependency in the pom.xml (I'm using spring 3.1.0.RELEASE):

        <dependency>
            <groupId>org.codehaus.jackson</groupId>
            <artifactId>jackson-mapper-asl</artifactId>
            <version>1.9.10</version>
        </dependency>
    

    The mapping in rest-servlet.xml

    <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
       <property name="messageConverters">
           <list>
               <ref bean="jsonConverter" />
           </list>
       </property>
    </bean>
    
    <bean id="jsonConverter" class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
       <property name="supportedMediaTypes" value="application/json" />
    </bean>
    

    And the header of the controller method:

    public @ResponseBody SimpleMessage subscribeUser(@RequestBody Subscription subscription)
    

    As a result of the POST I receive a 400 Incorrect request error. Is it possible to do this or do i need to do it with @RequestBody String or @RequestBody Map<String,Object> and decode the JSON myself?

    Thanks!

    • M. Deinum
      M. Deinum over 10 years
      There should be nothing preventing you from doing this. Error 400 indicates something either wrong with the request or the mapping. Does the User class have a default no-arg constructor.
    • Admin
      Admin over 10 years
      The wrapper is an absolute different concept in Java, what you have a couple of objects that you want to aggregate in the outer class.
    • pappu_kutty
      pappu_kutty over 10 years
      what version of spring you are using?
    • Will Keeling
      Will Keeling over 10 years
      Can you post the mapping for that controller method and the code that is sending the JSON?
    • mannuk
      mannuk over 10 years
      Give a minute I'm going to update the post
    • mannuk
      mannuk over 10 years
      The post is now updated. I included my pom dependencies, spring version and the converters. Hope it helps
  • mannuk
    mannuk over 10 years
    Yes, i have the dependency. It works if i send only the user which has an username, password... but if i try to send the subscription object is not mapped. So one approach if the subscription wrapper does not work is ResponseBody String, or ResponseBody Map<String,Object> and doing it by myself getting the elements and converting to my objects.
  • mvb13
    mvb13 over 10 years
    I didn't understand one detail. You said that when you send only User object(do you send it to another url?) it works, but when you send whole Subscription this doesn't work. Is this correct? If yes try to check mapping urls first, and HTTP reuest Content-Type specified to json second.
  • Admin
    Admin over 10 years
    Could you elaborate how the spring does it. Is the only Jackson integration is possible to convert it to JSON format. OP expect to populate the model with the @ResponseBody.
  • mvb13
    mvb13 over 10 years
    I don't know how Spring does the conversion. I didn't see this in documentation. Documentation only says that you must have jackson dependency added to your project. So this is Spring documentation approach.
  • mannuk
    mannuk over 10 years
    @mvb13 I mean that if i replace the "Subscription" argument in the controller by the argument "User" and I send the json including only the var manager the mapping is ok.
  • mvb13
    mvb13 over 10 years
    Ok. Show me User class please.
  • mannuk
    mannuk over 10 years
    would you mind to explain more your last lines? The configuration of the ObjectMapper and MappingJackson2HttpMessageConverter. In my case the objects has missing properties due to they are not necessary.
  • Sotirios Delimanolis
    Sotirios Delimanolis over 10 years
    @mannuk MappingJackson2HttpMessageConverter uses the new Jackson libraries with package com.fasterxml.jackson. ObjectMapper has a configure() method which you can use to configure deserialization and serialization features. You might want to look into some of them.
  • mannuk
    mannuk over 10 years
    do you know any website where i can check it? I would like to learn more about the converter and the configure method. I will test your answer on Monday at work and then I'll be able to tell you my progress
  • mannuk
    mannuk over 10 years
    I checked what you recommended me. I created the SusbcriptionWrapper but the 400 error persists. Furthermore, I can't use MappingJackson2HttpMessageConverter because is used by Spring 3.2 but I have to use Spring 3.1. Is there anything i can do?
  • Sotirios Delimanolis
    Sotirios Delimanolis over 10 years
    @mannuk The class is available since Spring 3.1.2. Use a debugger, step through the MappingJackson2HttpMessageConverter and see what field is blocking the deserialization. You can also set your log level to trace to get more details.