[Java Spring Boot] How to implement a Custom Serializer for your Responses or Json

brunbs

Bruno Barbosa

Posted on November 7, 2023

[Java Spring Boot] How to implement a Custom Serializer for your Responses or Json

Clique aqui para versão em português

This article complements the previous article, which talks about creating a customizable deserializer for requests, and you can read it by clicking here.

While in the previous article we talked about deserialization (json --> java object), in this one we will cover serialization (java object --> json).

Problem description:

Imagine that you are implementing a system that uses an event-based architecture with messaging, for example, Kafka.
So you could have something like this:

a) A producer, responsible for creating a json (message) that is sent to a messaging topic.
b) A consumer, who will be "listening" to a specific messaging topic, taking messages from the queue and passing them on to the next API, which we will call integrator.
c) Integrating API, responsible for applying business rules to persist the database.

Okay, with this architecture defined and implemented, let's imagine the following scenario (exactly the same as the one defined in the last article):
You need to modify the system so that all String information is persisted in the database in upper case.

Could you simply use the string class's toUpperCase() method on all fields before persisting? Of course, yes you could. But imagine that you have many fields.

I went through this experience and we had more than 200 fields to make this change, leading to a waste of time and excessively large lines of code (in addition to being very annoying, let's face it).

In the last post I showed how we could make the request that, in this case, would arrive at the integrator API, have string fields deserialized in a customized way, passing through toUpperCase at the application input. Here I'm going to show a different way: let's take our producer application and make it serialize the information using a custom serializer to make the toUpperCase for us. Thus, the message in the messaging will already have the values ​​of the string fields in upperCase, which will be received by the consumer and passed on to the integrator, who will not need to worry about carrying out this type of treatment, just applying other business rules and persisting the data .

This will be possible because Jackson (standard library for serializing and deserializing java objects to json) uses ObjectMapper to serialize objects. Thus, we can add a custom module to the object mapper to serialize specific types of data, in this case, strings.

Base Scenario:

As the basis of the application, we will use a small API that receives student data in its request and then sends a response with that same data. We will not focus on business rules, persistence, messaging communications as this is not our focus, we will talk directly about customized object serialization.

Controller Class:

It receives a request at the POST /students endpoint and returns an OK status with the serialized object (A String). This string returned from the service is the result of the serialization done by the object mapper that you will see in the implementation of the service class.
As we are not going to implement messaging here, this String returned from the endpoint will represent the JSON sent to the messaging.



@RestController
public class StudentController {

    @Autowired
    private StudentService studentService;

    @PostMapping("/students")
    ResponseEntity<String> createStudent(@RequestBody StudentRequest studentRequest) {

        String studentJson = studentService.createStudent(studentRequest);

        return ResponseEntity.ok().body(studentJson);
    }

}


Enter fullscreen mode Exit fullscreen mode

Request Class:



@Data
public class StudentRequest {

    private Long registration;
    private String name;
    private String lastName;

}


Enter fullscreen mode Exit fullscreen mode

Service Class:



@Service
public class StudentServiceImpl implements StudentService {

    @Autowired
    private StudentMapper studentMapper;

    @Override
    public String createStudent(StudentRequest studentRequest) {

        //some business logics
        ObjectMapper mapper = new ObjectMapper();
        String json = mapper.writeValueAsString(studentRequest);

        //send json to messaging (kafka)

        return json;
    }
}


Enter fullscreen mode Exit fullscreen mode

This implementation as is generates the following result:

POSTMAN screenshot showing a request with lower case values ​​returning a response with lower case values ​​too

Now let's move on to our implementation:

Implementing the Custom Serializer:

Let's start by creating a class that will be responsible for overriding the object mapper's default string object serialization behavior. Let's call this class UpperCaseSerializer.class:



import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;

import java.io.IOException;

public class UpperCaseSerializer extends JsonSerializer<String> {


    @Override
    public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        if(s != null && !s.isEmpty() && !s.equals("null")) {
            jsonGenerator.writeString(s.toUpperCase());
        } else {
            jsonGenerator.writeNull();
        }
    }

    @Override
    public Class<String> handledType() {
        return String.class;
    }
}



Enter fullscreen mode Exit fullscreen mode

Note that it extends the JsonSerializer class by passing within the diamond operator (<>) the type of object we want to implement our serializer, in this case, String.

Then we override the serialize method, which receives the String to be serialized, a JsonGenerator and a SerializerProvider.
We then check for null or empty fields and, if it is not null or empty, the jsonGenerator will write the String (writeString method) receiving our string(s) with the toUpperCase() method, that is, it will write our String in the value of each string field as uppercase.

If it is empty or null, it will write null.

Okay, now that we've written our custom Serialization class, just run the program? Not yet, as we have to tell the object mapper that it should use this class. We will do this in our service, right after instantiating the object mapper.

StudentServiceImpl Class:



import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.seralization.example.request.StudentRequest;
import com.seralization.example.service.StudentService;
import com.seralization.example.util.StudentMapper;
import com.seralization.example.util.UpperCaseSerializer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class StudentServiceImpl implements StudentService {

    @Autowired
    private StudentMapper studentMapper;

    @Override
    public String createStudent(StudentRequest studentRequest) {
        try {
            //some business logics

            ObjectMapper mapper = new ObjectMapper();
            mapper.registerModule(new SimpleModule().addSerializer(new UpperCaseSerializer()));
            String json = mapper.writeValueAsString(studentRequest);

            //send json to messaging (kafka)

            return json;
        } catch (JsonProcessingException e) {
            return "";
        }
    }
}



Enter fullscreen mode Exit fullscreen mode

Notice that right after initializing the objectmapper we added a new module to it, pointing to our upper case serializer.

Once this is done, we return this created json and the controller will show it to us in the endpoint response.

Let's see the result!

Result:

POSTMAN screenshot showing a request with lower case values ​​returning a response with upper case values

It worked perfectly well for what we proposed.
Remembering that you can create custom serializer for other types of data too, as you prefer! And this isn't the only way to do it and I don't even know if it's the best, but it was the one that helped me when I needed it.

That's it for now, if you have suggestions, questions or comments, just comment or call me.
If you want to see the repository of this code, it can be found here.

This repository has the base code in the main branch, a branch implementing our serializer that we saw in this article and also a branch implementing a deserializer that we saw in the previous article.

I hope you liked it and that it was useful to you.

💖 💪 🙅 🚩
brunbs
Bruno Barbosa

Posted on November 7, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related