32

I have

@RequestMapping(method = RequestMethod.GET)
@ResponseBody
SessionInfo register(UserProfile profileJson){
  ...
}

I pass profileJson this way:

http://server/url?profileJson={"email": "mymail@gmail.com"}

but my profileJson object has all null fields. What should I do to make spring parse my json?

Stepan Yakovenko
  • 6,197
  • 19
  • 92
  • 176
  • Passing JSON into the query parameter doesn't make sense. You need to at least URL-encode it. – ashes999 Feb 05 '14 at 13:25
  • 1
    You are passing json as a URL parameter not as the body (which is the default). Passing JSON as a parameter in general doesn't make sense. Annotate your method argument with `@RequestParam`. However as mentioned you should be passing it as the body of the request and probably also as a POST instead of a GET request. – M. Deinum Feb 05 '14 at 14:00
  • 1
    you almost certainly want to use POST here, having request bodies for a get is highly uncommon (http://stackoverflow.com/questions/978061/http-get-with-request-body) – chk Feb 05 '14 at 14:00
  • 1
    I use jsonp, it doesn't support POST. Annotating parameter with @RequestParam gives exception 'no matching editors or conversion strategy found' – Stepan Yakovenko Feb 05 '14 at 14:49
  • Just get the parameter as a `String` and convert it yourself. – Sotirios Delimanolis Feb 05 '14 at 15:03
  • Dear Sotirios, this is how I do it currently, but after code review I was asked to do it elegantly. – Stepan Yakovenko Feb 05 '14 at 18:52
  • (When replying to someone, use @their-name. Otherwise, they won't get a notification.) Alternatively, you can write your `HttpMessageConverter`, but it doesn't really make any sense to use `@RequestBody` if the content you want is not in the body. – Sotirios Delimanolis Feb 05 '14 at 20:42

5 Answers5

30

The solution to this is so easy and simple it will practically make you laugh, but before I even get to it, let me first emphasize that no self-respecting Java developer would ever, and I mean EVER work with JSON without utilizing the Jackson high-performance JSON library.

Jackson is not only a work horse and a defacto JSON library for Java developers, but it also provides a whole suite of API calls that makes JSON integration with Java a piece of cake (you can download Jackson at http://jackson.codehaus.org/).

Now for the answer. Assuming that you have a UserProfile pojo that looks something like this:

public class UserProfile {

private String email;
// etc...

public String getEmail() {
    return email;
}

public void setEmail(String email) {
    this.email = email;
}

// more getters and setters...
}

...then your Spring MVC method to convert a GET parameter name "profileJson" with JSON value of {"email": "mymail@gmail.com"} would look like this in your controller:

import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper; // this is your lifesaver right here

//.. your controller class, blah blah blah

@RequestMapping(value="/register", method = RequestMethod.GET) 
public SessionInfo register(@RequestParam("profileJson") String profileJson) 
throws JsonMappingException, JsonParseException, IOException {

    // now simply convert your JSON string into your UserProfile POJO 
    // using Jackson's ObjectMapper.readValue() method, whose first 
    // parameter your JSON parameter as String, and the second 
    // parameter is the POJO class.

    UserProfile profile = 
            new ObjectMapper().readValue(profileJson, UserProfile.class);

        System.out.println(profile.getEmail());

        // rest of your code goes here.
}

Bam! You're done. I would encourage you to look through the bulk of Jackson API because, as I said, it is a lifesaver. For example, are you returning JSON from your controller at all? If so, all you need to do is include JSON in your lib, and return your POJO and Jackson will AUTOMATICALLY convert it into JSON. You can't get much easier than that. Cheers! :-)

The Saint
  • 416
  • 3
  • 9
  • 1
    I wish here was some parameter for the @RequestParameter annotation for this. – Jaroslav Záruba Apr 07 '15 at 10:00
  • 3
    It's not good practice to create for every request ObjectMapper. Controller should have field ObjectMapper. – Vladislav Koroteev Jun 17 '15 at 12:16
  • 6
    Actually, that's incorrect. First of all, I'm assuming by "field" you mean an instance variable of the controller class. Secondly, the point of my example was to demonstrate the use of ObjectMapper, not give best practices and delve into finer architecture details of object instantiation. And thirdly, and most importantly, having ObjectMapper as an instance variable of a controller is just flat-out bad practice because Spring controllers are singletons by default. The correct approach would be follow standard MVC patterns and Autowire a service class which contains an ObjectMapper. – The Saint Jun 17 '15 at 20:36
  • 1
    @TheSaint I'm just being curious, why it's bad practice for singleton controller to have a field like this? – grape_mao Oct 25 '16 at 08:44
  • @grape_mao, the ObjectMapper is not considered to be fully thread-safe in Jackson 2.x, according to its author: https://stackoverflow.com/a/3909846/1199132 – Xtreme Biker Jan 16 '18 at 13:55
  • 3
    It would be more elegant (and probably more efficient) to create a `Converter` and let Spring MVC use it automatically. See my [answer](https://stackoverflow.com/a/50350428/238134) for an example. – deamon May 15 '18 at 12:40
  • @theSaint In which dependency do I find SessionInfo? – conteh Sep 16 '20 at 17:14
27

This could be done with a custom editor, that converts the JSON into a UserProfile object:

public class UserProfileEditor extends PropertyEditorSupport  {

    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        ObjectMapper mapper = new ObjectMapper();

        UserProfile value = null;

        try {
            value = new UserProfile();
            JsonNode root = mapper.readTree(text);
            value.setEmail(root.path("email").asText());
        } catch (IOException e) {
            // handle error
        }

        setValue(value);
    }
}

This is for registering the editor in the controller class:

@InitBinder
public void initBinder(WebDataBinder binder) {
    binder.registerCustomEditor(UserProfile.class, new UserProfileEditor());
}

And this is how to use the editor, to unmarshall the JSONP parameter:

@RequestMapping(value = "/jsonp", method = RequestMethod.GET, produces = {MediaType.APPLICATION_JSON_VALUE})
@ResponseBody
SessionInfo register(@RequestParam("profileJson") UserProfile profileJson){
  ...
}
Angular University
  • 38,399
  • 15
  • 70
  • 79
  • 1
    This looks so generic, I wonder if it's really necessary to write and register a custom editor for every class I want to be parsed that way. – Rüdiger Schulz Sep 09 '14 at 14:43
  • 3
    Adding this to the dispatcher servlet xml worked for me ' ' – Yahiya Oct 24 '14 at 13:39
  • 1
    It can be even simpler with a `Converter`. See my [answer for an example](https://stackoverflow.com/a/50350428/238134). – deamon May 15 '18 at 12:42
5

You can create your own Converter and let Spring use it automatically where appropriate:

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;

@Component
class JsonToUserProfileConverter implements Converter<String, UserProfile> {

    private final ObjectMapper jsonMapper = new ObjectMapper();

    public UserProfile convert(String source) {
        return jsonMapper.readValue(source, UserProfile.class);
    }
}

As you can see in the following controller method nothing special is needed:

@GetMapping
@ResponseBody
public SessionInfo register(@RequestParam UserProfile userProfile)  {
  ...
}

Spring picks up the converter automatically if you're using component scanning and annotate the converter class with @Component.

Learn more about Spring Converter and type conversions in Spring MVC.

deamon
  • 78,414
  • 98
  • 279
  • 415
  • 1
    Given that this looks quite generic, can we just parametrize the class? `class JsonToUserProfileConverter implements Converter` Will Spring still automatically pick this class up and use it (by inferring T from the result type)? – Sudix Mar 28 '20 at 04:29
  • 1
    Spring resolves type parameters, so that should work as expected (only exception are nested type parameters with Kotlin types, what not always works). – deamon Jun 08 '20 at 08:13
2

This does solve my immediate issue, but I'm still curious as to how you might pass in multiple JSON objects via an AJAX call.

The best way to do this is to have a wrapper object that contains the two (or multiple) objects you want to pass. You then construct your JSON object as an array of the two objects i.e.

[
  {
    "name" : "object1",
    "prop1" : "foo",
    "prop2" : "bar"
  },
  {
    "name" : "object2",
    "prop1" : "hello",
    "prop2" : "world"
  }
]

Then in your controller method you recieve the request body as a single object and extract the two contained objects. i.e:

@RequestMapping(value="/handlePost", method = RequestMethod.POST, consumes = {      "application/json" })
public void doPost(@RequestBody WrapperObject wrapperObj) { 
     Object obj1 = wrapperObj.getObj1;
     Object obj2 = wrapperObj.getObj2;

     //Do what you want with the objects...


}

The wrapper object would look something like...

public class WrapperObject {    
private Object obj1;
private Object obj2;

public Object getObj1() {
    return obj1;
}
public void setObj1(Object obj1) {
    this.obj1 = obj1;
}
public Object getObj2() {
    return obj2;
}
public void setObj2(Object obj2) {
    this.obj2 = obj2;
}   

}
Zigri2612
  • 1,832
  • 16
  • 30
-7

Just add @RequestBody annotation before this param

  • This doesn't work. I get HTTP 415: The server refused this request because the request entity is in a format not supported by the requested resource for the requested method. – Stepan Yakovenko Feb 05 '14 at 13:35
  • 2
    `@RequestBody` by default looks at the body of the request. OP's content is a request parameter sent as a part of the query string. – Sotirios Delimanolis Feb 05 '14 at 14:55