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.
2. The Problem
First, let’s look at the limitations of Spring MVC’s default approach to JSON deserialization.
2.1. The Default @RequestBody
Behavior
Let’s start with an example JSON body:
Next, let’s create DTOs that match the JSON input:
Finally, we’ll use the standard approach for deserializing our JSON request into a UserDto
using the @RequestBody
annotation:
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.
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.
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.
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:
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:
|
|
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.
Let’s define an implementation of HandlerMethodArgumentResolver
that will process all request handler parameters annotated with @JsonArg
:
|
|
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.
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:
|
|
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:
|
|
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:
|
|
Next, let’s write a test where we call the second endpoint that parses the JSON directly into POJOs:
|
|
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.
As always, the code for these examples is available over on GitHub.
Reference https://www.baeldung.com/spring-mvc-json-param-mapping/