Spring JSON request body not mapped to Java POJO
Asked Answered
F

6

31

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.

Fenland answered 15/8, 2016 at 17:42 Comment(18)
What about if you flatten that json: {"firstname":"xyz","lastname":"XYZ"}Codex
Hi @mszymborski, I've tried that but didn't work either. The default constructor was used.Fenland
Instead of using headers try using consumes attribute in the @RequestMapping.Wilmawilmar
The problem lies outside of Jackson, then - raw ObjectMapper works just fine with the flattened version.Codex
Hi @11thdimension, Tried that too. Didn't help.Fenland
What happens when you remove both the constructors ?Wilmawilmar
@Y.Chen: something's wrong with your setter - it's called getLastname - does anything change if you fix it?Codex
@Codex strangely enough Jackson is able to deserialize correct values even with the incorrect/absent setter.Wilmawilmar
@11thdimension: I've noticed, isn't that strange, though? I'd prefer a fail-fast behaviour in that case.Codex
@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.Codex
@Codex I agree that the problem could be with my Spring configuration. Am I supposed to create a bean for CustomerInfo class?Fenland
@Wilmawilmar The program would complain about "no default constructor found"...Fenland
@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.Codex
@Codex 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.Fenland
@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.Codex
thanks @Codex for all your comments. I got the correct results once I added @ RequestBody annotation to my method implementation as well as the interface.Fenland
@Y.Chen: ah, that was a tricky one. Make sure to post it as an answer!Codex
Thanks, wasted almost 3 hr to figure out the root cause, Finally google helped me to find your answer.Assured
F
21

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.

Fenland answered 16/8, 2016 at 15:17 Comment(0)
E
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

Equivocal answered 15/8, 2016 at 19:21 Comment(2)
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.Fenland
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?Colenecoleopteran
H
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.

Haematoblast answered 15/8, 2016 at 18:0 Comment(0)
J
1

I had a similar issue and the issue was not adding @JsonProperty in the DTO to the field.

@JsonProperty("UnitId")
private String UnitId;

@JsonProperty("ComponentId")
private String ComponentId;
Jasperjaspers answered 30/10, 2023 at 12:25 Comment(1)
this worked form me, Thanks a lot saving many hours!Beast
W
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.
}
Woden answered 11/7, 2018 at 12:32 Comment(0)
F
-2

remove those two statements from default constructor and try

Foucault answered 16/8, 2016 at 12:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.