30

I'm using Spring to implement a RESTful web service. One of the endpoints takes in a JSON string as request body and I wish to map it to a POJO. However, it seems right now that the passed-in JSON string is not property mapped to the POJO.

here's the @RestController interface

@RequestMapping(value="/send", headers="Accept=application/json", method=RequestMethod.POST)
public void sendEmails(@RequestBody CustomerInfo customerInfo);

the data model

public class CustomerInfo {
    private String firstname;
    private String lastname; 
    public CustomerInfo() {
        this.firstname = "first";
        this.lastname = "last";
    }

    public CustomerInfo(String firstname, String lastname)
    {
        this.firstname = firstname;
        this.lastname = lastname;
    }

    public String getFirstname(){
        return firstname;
    }

    public void setFirstname(String firstname){
        this.firstname = firstname;
    }

    public String getLastname(){
        return lastname;
    }

    public void getLastname(String lastname){
        this.lastname = lastname;
    }
}

And finally my POST request:

{"CustomerInfo":{"firstname":"xyz","lastname":"XYZ"}}

with Content-Type specified to be application/json

However, when I print out the object value, the default value("first" and "last") got printed out instead of the value I passed in("xyz" and "XYZ")

Does anyone know why I am not getting the result I expected?

FIX

So it turned out that, the value of request body is not passed in because I need to have the @RequestBody annotation not only in my interface, but in the actual method implementation. Once I have that, the problem is solved.

Y. Chen
  • 517
  • 1
  • 4
  • 10
  • What about if you flatten that json: `{"firstname":"xyz","lastname":"XYZ"}` – mszymborski Aug 15 '16 at 17:46
  • Hi @mszymborski, I've tried that but didn't work either. The default constructor was used. – Y. Chen Aug 15 '16 at 17:48
  • Instead of using `headers` try using `consumes` attribute in the `@RequestMapping`. – 11thdimension Aug 15 '16 at 17:51
  • The problem lies outside of Jackson, then - raw ObjectMapper works just fine with the flattened version. – mszymborski Aug 15 '16 at 17:54
  • Hi @11thdimension, Tried that too. Didn't help. – Y. Chen Aug 15 '16 at 17:56
  • What happens when you remove both the constructors ? – 11thdimension Aug 15 '16 at 17:57
  • @Y.Chen: something's wrong with your setter - it's called `getLastname` - does anything change if you fix it? – mszymborski Aug 15 '16 at 18:05
  • @mszymborski strangely enough Jackson is able to deserialize correct values even with the incorrect/absent setter. – 11thdimension Aug 15 '16 at 18:13
  • @11thdimension: I've noticed, isn't that strange, though? I'd prefer a fail-fast behaviour in that case. – mszymborski Aug 15 '16 at 18:14
  • @Y.Chen: I've created a simple Spring boot app which has a rest controller like yours, I'm not able to reproduce the behaviour you've described. It could be boot's magic in play, or there's something more than what's included in the question. – mszymborski Aug 15 '16 at 18:20
  • @mszymborski I agree that the problem could be with my Spring configuration. Am I supposed to create a bean for CustomerInfo class? – Y. Chen Aug 15 '16 at 18:53
  • @11thdimension The program would complain about "no default constructor found"... – Y. Chen Aug 15 '16 at 18:54
  • @Y.Chen: nope, it should work right off the bat. I have just the default boot config + your controller (which returns a string with these two values in plaintext) + the class itself (copied byte to byte). It's really strange. – mszymborski Aug 15 '16 at 18:56
  • @mszymborski I appreciate your help! Would you please share your servlet.xml file and the dependency files, if you happen to use maven or gradle? I would like to compare those with mine. – Y. Chen Aug 15 '16 at 19:07
  • @Y.Chen: the problem with spring boot is that it configures these things using some defaults, and unfortunately currently I don't have access to my personal bare Spring projects to test the explicit configuration. – mszymborski Aug 15 '16 at 19:14
  • thanks @mszymborski for all your comments. I got the correct results once I added @ RequestBody annotation to my method implementation as well as the interface. – Y. Chen Aug 15 '16 at 23:33
  • @Y.Chen: ah, that was a tricky one. Make sure to post it as an answer! – mszymborski Aug 16 '16 at 00:03
  • Thanks, wasted almost 3 hr to figure out the root cause, Finally google helped me to find your answer. – Vinayak Dornala Feb 13 '19 at 00:57

5 Answers5

20

So it turned out that, the value of request body is not passed in because I need to have the @RequestBody annotation not only in my interface, but in the actual method implementation. Once I have that, the problem is solved.

Y. Chen
  • 517
  • 1
  • 4
  • 10
9

You can do it in many ways, Here i am going to do it in below different ways-

NOTE: request data shuld be {"customerInfo":{"firstname":"xyz","lastname":"XYZ"}}

1st way We can bind above data to the map as below

@RequestMapping(value = "/send", headers = "Accept=application/json", method = RequestMethod.POST)
public void sendEmails(@RequestBody HashMap<String, HashMap<String, String>> requestData) {

    HashMap<String, String> customerInfo = requestData.get("customerInfo");
    String firstname = customerInfo.get("firstname");
    String lastname = customerInfo.get("lastname");
    //TODO now do whatever you want to do.
}

2nd way we can bind it directly to pojo

step 1 create dto class UserInfo.java

public class UserInfo {
    private CustomerInfo customerInfo1;

    public CustomerInfo getCustomerInfo1() {
        return customerInfo1;
    }

    public void setCustomerInfo1(CustomerInfo customerInfo1) {
        this.customerInfo1 = customerInfo1;
    }
}

step 1. create another dto classCustomerInfo.java

class CustomerInfo {
        private String firstname;
        private String lastname;

        public String getFirstname() {
            return firstname;
        }

        public void setFirstname(String firstname) {
            this.firstname = firstname;
        }

        public String getLastname() {
            return lastname;
        }

        public void setLastname(String lastname) {
            this.lastname = lastname;
        }
    }

step 3 bind request body data to pojo

 @RequestMapping(value = "/send", headers = "Accept=application/json", method = RequestMethod.POST)
    public void sendEmails(@RequestBody UserInfo userInfo) {

        //TODO now do whatever want to do with dto object
    }

I hope it will be help you out. Thanks

sanjeevjha
  • 1,439
  • 14
  • 18
  • Thank you so much for the very detailed answer. All the ways you suggested will do the conversion trick. Although they are not the solutions to my problem, I'm still glad to learn all those. – Y. Chen Aug 15 '16 at 23:28
  • Is there a way to bind it simply to the method parameter name instead of introducing a new java class or mapping it arbitrarily to a map of key-value pairs? – user1048931 Jan 08 '18 at 18:49
1

The formatting on this is terrible, but this should work for jackson configuration.

<!-- Use Jackson for JSON conversion (POJO to JSON outbound). -->
<bean id="jsonMessageConverter"
            class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/> 

<!-- Use JSON conversion for messages -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    <property name="messageConverters">
        <list>
            <ref bean="jsonMessageConverter"/>
        </list>
    </property>
</bean>

ALso, as mentioned in a comment, your JSON is wrong for your object.

{"firstname":"xyz",‌​"lastname":"XYZ"}

does appear to be the correct JSON for your object.

DwB
  • 37,124
  • 11
  • 56
  • 82
0

Sample Data :

[  
{  
  "targetObj":{  
     "userId":1,
     "userName":"Devendra"
  }
},
{  
  "targetObj":{  
     "userId":2,
     "userName":"Ibrahim"
  }
},
{  
  "targetObj":{  
     "userId":3,
     "userName":"Suraj"
  }
}
]

For above data this pring controller method working for me:

@RequestMapping(value="/saveWorkflowUser", method = RequestMethod.POST)
public void saveWorkflowUser (@RequestBody List<HashMap<String ,HashMap<String , 
  String>>> userList )  {
    System.out.println(" in saveWorkflowUser : "+userList);
 //TODO now do whatever you want to do.
}
Lorelorelore
  • 3,335
  • 8
  • 29
  • 40
-2

remove those two statements from default constructor and try

Anil
  • 191
  • 1
  • 2
  • 11