[Java SpringBoot] How to implement a Custom Deserializer for your Requests
Bruno Barbosa
Posted on November 3, 2023
Clique aqui para versão em português
Introduction
Some days ago a situation arose to be resolved at work and I decided to share the solution here on dev.to!
I'll try to summarize the problem as much as I can:
We had a Java SpringBoot API that persisted information in a PostgreSQL database.
This API received an object in the request that was made up of around 200 fields, most of them were strings.
Some time after the development was completed, the need arose for all textual information to be persisted in upper case (LIKE THIS, WITH CAPS LOCK).
The team then met to decide which approach we would follow to modify the API in question.
The first proposed alternative was to receive all the fields and manually use the toUpperCase() method of the String class to convert attribute by attribute to upper case.
As the API had a certain complexity, the time estimated by TL for the task was approximately 1 week.
As we work with tight deadlines, I thought of another approach to reduce the time cost of the solution: make a custom deserializer so that all string fields are converted to upper case upon arrival of the request, when mapped to an object at the input of the API endpoint.
This development took about 30 minutes to complete, between research and development, saving the team almost 1 week of work and worked very well.
I will share with you what was developed using a small use case example API. I would like to make it clear that the objective here is not whether this is the best practice (that's up to you to decide with your team according to your needs and availability), I also didn't worry about making the code more foolproof here in the demonstration, so I didn't include, for example, try catch because this is not the focus of this article.
For those who want to see the **repository **of the code used, here is the link:
GitHub Repository
In the repository you will find the main branch with the project's base code before implementation.
You will also find the branch where the custom deserializer was implemented and, also, a branch where I implemented a custom serializer if you want the application to be output, when assembling the JSON that comes out of your application.
(I'll talk about the serializer in the next article!)
Introducing the application:
To give an example, I created a small application that has the POST /students endpoint as its input port, which expects to receive a JSON in the body of the request that will be mapped to an object of the StudentRequest class. Below is the code for the StudentController.java class
@RestController
public class StudentController {
@Autowired
private StudentService studentService;
@PostMapping("/students")
ResponseEntity<StudentResponse> createStudent(@RequestBody StudentRequest studentRequest) {
return ResponseEntity.ok().body(studentService.createStudent(studentRequest));
}
}
We can see that this class is receiving injection from the service that will handle the business rules.
Our Request class looks like this (StudentRequest.java):
@Data
public class StudentRequest {
private Long registration;
private String name;
private String lastName;
}
The response has the same fields, but with the name StudentResponse.
This service, StudentService.java, will have the business rules to then persist the Student information in the database. I didn't mind implementing persistence in the database because it's not the focus here, but basically we would have a service like this:
@Service
public class StudentServiceImpl implements StudentService {
@Autowired
private StudentMapper studentMapper;
@Override
public StudentResponse createStudent(StudentRequest studentRequest) {
//some business logics
// persistance ommited
return studentMapper.requestToResponse(studentRequest);
}
}
Note that here, to shorten transformations from the request class to the entity or response class, I am using MapStruct (I will write about MapStruct in another article).
When we run the project, we can see the following behavior:
Right, the code written this way (which you can see in the main branch of the repository) ends up persisting the information as it arrives in the request, without any treatment or conversion of the fields into upper case as we want. Let's go, then, to the implementation of our Deserializer, so that the String fields go through toUpperCase() immediately upon entering the request.
Implementing the Deserializer:
This solution is incredibly simple, let's just create a class called ToUpperCaseConverter.java and make it extend Jackson's StdConverter class.
Inside the diamond operator ( <> ) we will place two types of object, one for input (IN) and one for output (OUT), that is, the StdConverter has as its "signature", StdConverter. As we are going to receive a String and return a String but in upper case, we will use StdConverter.
And then we will override the convert(String value) method by placing our toUpperCase() in the implementation of this method. Our class looks like this:
import com.fasterxml.jackson.databind.util.StdConverter;
public class ToUpperCaseConverter extends StdConverter<String, String> {
@Override
public String convert(String value) {
return value.toUpperCase();
}
}
This class, for organization purposes, I placed inside a package called utils.
Now, how do we mark the fields that need to undergo this conversion? To do this, we just go to our request class and add the @JsonDeserialize annotation and pass to it which class will apply the custom deserialization we created, that is, the ToUpperCaseConverter.java class. Let's add this annotation to all the fields that we need to perform this conversion, thus creating our Request class:
@Data
public class StudentRequest {
private Long registration;
@JsonDeserialize(converter = ToUpperCaseConverter.class)
private String name;
@JsonDeserialize(converter = ToUpperCaseConverter.class)
private String lastName;
}
Now, we can run our API and see the result, as shown in the POSTMAN screenshot below:
You can make many adaptations to this logic, including creating a custom Deserializer for the entire Request class! Going field by field instructing what manipulation you want to do with the field, but that's for another post.
I hope you were able to contribute and questions and suggestions can be sent here, I would be very grateful if you have improvements to suggest.
Posted on November 3, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.