Map a JSON POST to Multiple Spring MVC Parameters – 将一个JSON POST映射到多个Spring MVC参数上

最后修改: 2022年 9月 19日

1. Overview


When using Spring’s default support for JSON deserialization, we’re forced to map the incoming JSON to a single request handler parameter. Sometimes, however, we’d prefer a more fine-grained method signature.


In this tutorial, we will learn how to use a custom HandlerMethodArgumentResolver to deserialize a JSON POST into multiple strongly-typed parameters.

在本教程中,我们将学习如何使用一个自定义的HandlerMethodArgumentResolver来将JSON POST反序列化为多个强类型的参数。

2. The Problem


First, let’s look at the limitations of Spring MVC’s default approach to JSON deserialization.

首先,我们来看看Spring MVC默认的JSON反序列化方法的局限性。

2.1. The Default @RequestBody Behavior


Let’s start with an example JSON body:


   "firstName" : "John",
   "lastName"  :"Smith",
   "age" : 10,
   "address" : {
      "streetName" : "Example Street",
      "streetNumber" : "10A",
      "postalCode" : "1QW34",
      "city" : "Timisoara",
      "country" : "Romania"

Next, let’s create DTOs that match the JSON input:


public class UserDto {
    private String firstName;
    private String lastName;
    private String age;
    private AddressDto address;

    // getters and setters
public class AddressDto {

    private String streetName;
    private String streetNumber;
    private String postalCode;
    private String city;
    private String country;

    // getters and setters

Finally, we’ll use the standard approach for deserializing our JSON request into a UserDto using the @RequestBody annotation:


public class UserController {

    public ResponseEntity process(@RequestBody UserDto user) {
        /* business processing */
        return ResponseEntity.ok()

2.2. Limitations


The primary benefit of the standard solution above is that we don’t have to deserialize the JSON POST into a UserDto object manually.

上述标准解决方案的主要好处是,我们不必手动将JSON POST反序列化为一个UserDto对象。

However, the entire JSON POST must be mapped to a single request parameter. This means we have to create a separate POJO for each expected JSON structure, polluting our code base with classes used solely for this purpose.

然而,整个JSON POST必须映射到一个请求参数。这意味着我们必须为每个预期的JSON结构创建一个单独的POJO,用仅用于此目的的类污染我们的代码库。

That consequence is especially evident when we only need a subset of the JSON properties. In our request handler above, we only need the user’s firstName and city properties, but we’re forced to deserialize an entire UserDto.


While Spring allows us to use Map or ObjectNode as a parameter rather than a homegrown DTO, both are single-parameter options. As with a DTO, everything is packaged together. Since the Map and ObjectNode contents are String values, we must marshal them into objects ourselves. These options save us from declaring single-use DTOs but create even more complexity.


3. Custom HandlerMethodArgumentResolver


Let’s look at a solution to the limitations above. We can use Spring MVC’s HandlerMethodArgumentResolver to allow us to declare just the desired JSON attributes as parameters in our request handler.

让我们来看看解决上述限制的方法。我们可以使用Spring MVC的HandlerMethodArgumentResolver来允许我们在请求处理程序中只声明所需的JSON属性作为参数。

3.1. Creating the Controller


First, let’s create a custom annotation we can use to map a request handler parameter to a JSON path:


public @interface JsonArg {
    String value() default "";

Next, we’ll create a request handler that uses the annotation to map firstName and city as separate parameters that correlate to properties from our JSON POST body:

接下来,我们将创建一个请求处理程序,使用注解将firstNamecity映射为单独的参数,与我们的JSON POST主体的属性相关。

public class UserController {
    public ResponseEntity process(@JsonArg("firstName") String firstName,
      @JsonArg("") String city) {
        /* business processing */
        return ResponseEntity.ok()
            .body(String.format("{\"firstName\": %s, \"city\" : %s}", firstName, city));

3.2. Creating the Custom HandlerMethodArgumentResolver


After Spring MVC has decided which request handler should handle an incoming request, it attempts to resolve the parameters automatically. This includes iterating through all beans in the Spring context that implement the HandlerMethodArgumentResolver interface in case that can resolve any parameters Spring MVC can’t do automatically.

在Spring MVC决定哪个请求处理程序应该处理传入的请求后,它试图自动解决参数。这包括遍历Spring上下文中所有实现HandlerMethodArgumentResolver接口的Bean,以防止Spring MVC不能自动解决的参数。

Let’s define an implementation of HandlerMethodArgumentResolver that will process all request handler parameters annotated with @JsonArg:


public class JsonArgumentResolver implements HandlerMethodArgumentResolver {

    private static final String JSON_BODY_ATTRIBUTE = "JSON_REQUEST_BODY";

    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(JsonArg.class);

    public Object resolveArgument(
      MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
      WebDataBinderFactory binderFactory) 
      throws Exception {
        String body = getRequestBody(webRequest);
        String jsonPath = Objects.requireNonNull(
        Class<?> parameterType = parameter.getParameterType();
        return JsonPath.parse(body).read(jsonPath, parameterType);

    private String getRequestBody(NativeWebRequest webRequest) {
        HttpServletRequest servletRequest = Objects.requireNonNull(
        String jsonBody = (String) servletRequest.getAttribute(JSON_BODY_ATTRIBUTE);
        if (jsonBody == null) {
            try {
                jsonBody = IOUtils.toString(servletRequest.getInputStream());
                servletRequest.setAttribute(JSON_BODY_ATTRIBUTE, jsonBody);
            } catch (IOException e) {
                throw new RuntimeException(e);
        return jsonBody;

Spring uses the supportsParameter() method to check whether this class can resolve a given parameter. Since we want our handler to process any parameter annotated with @JsonArg, we return true if the given parameter has that annotation.

Spring使用supportsParameter() 方法来检查这个类是否可以解决一个给定的参数。因为我们希望我们的处理程序可以处理任何带有@JsonArg注释的参数,如果给定的参数有这个注释,我们就返回true

Next, in the resolveArgument() method, we extract the JSON body and then attach it as an attribute to the request so we can access it directly for subsequent calls. We then grab the JSON path from the @JsonArg annotation and use reflection to get the parameter’s type. With the JSON path and the parameter type information, we can deserialize discrete parts of the JSON body into rich objects.


3.3. Registering the Custom HandlerMethodArgumentResolver


For Spring MVC to use our JsonArgumentResolver, we need to register it:

为了让Spring MVC使用我们的JsonArgumentResolver,我们需要注册它。

public class WebConfig implements WebMvcConfigurer {

    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        JsonArgumentResolver jsonArgumentResolver = new JsonArgumentResolver();

Our JsonArgumentResolver will now process all request handler parameters annotated with @JsonArgs. We’ll need to ensure the @JsonArgs value is a valid JSON path, but that is a lighter process than the @RequestBody approach that requires a separate POJO for every JSON structure.


3.4. Using Parameters With Custom Types


To show that this will work with custom Java classes as well, let’s define a request handler with strongly-typed POJO parameters:


public ResponseEntity process(
  @JsonArg("firstName") String firstName, @JsonArg("lastName") String lastName,
  @JsonArg("address") AddressDto address) {
    /* business processing */
    return ResponseEntity.ok()
      .body(String.format("{\"firstName\": %s, \"lastName\": %s, \"address\" : %s}",
        firstName, lastName, address));

We can now map the AddressDto as a separate parameter.


3.5. Testing the Custom JsonArgumentResolver


Let’s write a test case to prove that the JsonArgumentResolver works as expected:


void whenSendingAPostJSON_thenReturnFirstNameAndCity() throws Exception {

    String jsonString = "{\"firstName\":\"John\",\"lastName\":\"Smith\",\"age\":10,\"address\":{\"streetName\":\"Example Street\",\"streetNumber\":\"10A\",\"postalCode\":\"1QW34\",\"city\":\"Timisoara\",\"country\":\"Romania\"}}";

Next, let’s write a test where we call the second endpoint that parses the JSON directly into POJOs:


void whenSendingAPostJSON_thenReturnUserAndAddress() throws Exception {
    String jsonString = "{\"firstName\":\"John\",\"lastName\":\"Smith\",\"address\":{\"streetName\":\"Example Street\",\"streetNumber\":\"10A\",\"postalCode\":\"1QW34\",\"city\":\"Timisoara\",\"country\":\"Romania\"}}";
    ObjectMapper mapper = new ObjectMapper();
    UserDto user = mapper.readValue(jsonString, UserDto.class);
    AddressDto address = user.getAddress();

    String mvcResult = mockMvc.perform(post("/user/process/custompojo").content(jsonString)

    assertEquals(String.format("{\"firstName\": %s, \"lastName\": %s, \"address\" : %s}",
      user.getFirstName(), user.getLastName(), address), mvcResult);

4. Conclusion


In this article, we looked at some limitations in Spring MVC’s default deserialization behavior and then learned how to use a custom HandlerMethodArgumentResolver to overcome them.

在这篇文章中,我们研究了Spring MVC默认反序列化行为的一些限制,然后学习了如何使用自定义HandlerMethodArgumentResolver来克服这些限制。

As always, the code for these examples is available over on GitHub.