7

How can I easily separate JSON values that are sent in the same request?

Given that I POST a JSON to my server:

{"first":"A","second":"B"}

If I implement the following method in the Controller:

@RequestMapping(value = "/path", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
public void handleRequest(@RequestBody String input) { 
    // ...
}

then the input parameter will constitute a String with the entire JSON object, {"first":"A","second":"B"}. What I really want is two separate Strings (or a String and an int whichever is suitable for the particular request) with just the two values (other key / value pairs that the client may send should be ignored).

If the strings were sent as request parameters instead of JSON request body it would be simple:

@RequestMapping(value = "/path", method = RequestMethod.POST)
public void handleRequest(@RequestParam("first") String first, 
                          @RequestParam("second") String second) { 
    // ...
}

I know that I can create a simple bean class that can be used in conjunction with the @RequestBody annotation that will contain both A and B when used, but it seems like a detour, since they will have different purposes inside the web app.

Dependencies: org.springframework : spring-web : 3.1.0.RELEASE org.codehaus.jackson : jackson-mapper-asl : 1.9.3

matsev
  • 32,104
  • 16
  • 121
  • 156

2 Answers2

5

POJO

public class Input {
    private String first;
    private String second;

    //getters/setters
}

...and then:

public void handleRequest(@RequestBody Input input)

In this case you need Jackson to be available on the CLASSPATH.

Map

public void handleRequest(@RequestBody Map<String, String> input)
Tomasz Nurkiewicz
  • 334,321
  • 69
  • 703
  • 674
  • 1
    The POJO corresponds to the bean class solution that I suggested (and that I should have written in the question). The Map may be a viable solution... – matsev Jan 20 '12 at 19:42
  • 3
    Instead of Map I would recommend using `JsonNode` as the intermediate type. Not a big deal, just bit more convenient, safe. – StaxMan Jan 21 '12 at 04:35
  • `JsonNode` looks interesting, especially if you need to convert the input to other types than Strings, or if the JSON structure is more complex than a one level key/value map. – matsev Jan 21 '12 at 12:58
  • What if you have two subtype classes that extend a supertype? i assume you cant put the parent class in the requestmapping method parameter? – Maxrunner May 14 '14 at 14:57
3

I have written a custom WebArgumentResolver that does exactly this, combined with a custom annotation.

I don't have the source available to me now, but basically I annotated my method like this:

@RequestMapping(value = "/path", method = RequestMethod.POST)
public void handleRequest(@JsonField("first") String first, @JsonField("second") String second) { 
    // ...
}

Then my JsonFieldWebArgumentResolver checks if the method parameter is annotated with JsonField, and if it is it extracts the actual type from the parameter (not quite straight-forward it turns out if you want to handle generic parameters as well, such as List<String> or List<Pojo>), and invokes Jackson's JsonParser manually to create the correct type. It's a shame I can't show you any code, but that's the gist of it.

However, that solution is for Spring MVC 3.0, and if you are using 3.1 I think you will be better off using a custom HandlerMethodArgumentResolver instead. But the idea should be the same.

waxwing
  • 18,547
  • 8
  • 66
  • 82
  • Elegant. An annotation like your `@JsonField` would be the perfect fit for my problem. I especially like the idea of making it generic, but I have to read up on this subject and on the `JsonNode` API that @StaxMan commented on. However, the clients currently posts really simple key/value Strings, so I have a hard time motivating the additional cost. I'll definitely keep this suggestion in mind. – matsev Jan 21 '12 at 12:58