[Java Spring Boot] Como Criar Serializador Personalizado para seus Responses ou Json de saída

brunbs

Bruno Barbosa

Posted on November 7, 2023

[Java Spring Boot] Como Criar Serializador Personalizado para seus Responses ou Json de saída

Este artigo complementa o artigo anterior, que fala sobre criar deserializador customizável para requests, e que você pode lê-lo clicando aqui.

Enquanto no artigo anterior falamos sobre deserialização (json --> objeto java), neste abordaremos a serialização (objeto java --> json).

Descrição do Problema:

Imagine que você está implementando um sistema que utiliza uma arquitetura baseada em eventos com mensageria, por exemplo, o Kafka.
Então você poderia ter algo parecido com isso:

a) Um producer, responsável por criar um json (mensagem) que é enviado a um tópico da mensageria.
b) Um consumer, que ficará "escutando" um determinado tópico da mensageria, pegando mensagens da fila e repassando para a próxima API, que vamos chamar de integradora.
c) API integradora, responsável por aplicar regras de negócio para fazer persistência no banco de dados.

Certo, com essa arquitetura definida e implementada, vamos imaginar o seguinte cenário (exatamente igual ao definido no último artigo):
Você precisa modificar o sistema para que todas as informações do tipo String sejam persistida no banco de dados em upper case.

Você poderia simplesmente utilizar o método toUpperCase() da classe string em todos os campos antes de persistir? Claro, poderia sim. Porém imagina que você tem muitos campos.

Passei por essa experiência e tinhamos mais de 200 campos para fazer essa alteração, levando a um gasto de tempo e de linhas de código demasiadamente grandes (além de ser muito chato, convenhamos).

Na última postagem mostrei como poderíamos fazer para que o request que, nesse caso, chegaria na API integradora, tivesse campos string deserializados de maneira customizada, passando pelo toUpperCase na entrada da aplicação. Aqui vou mostrar uma forma diferente: vamos pegar nossa aplicação producer e fazer com que ela serialize as informações utilizando um serializador personalizado para fazer o toUpperCase para nós. Assim a mensagem na mensageria já estará com os valores dos campos string em upperCase, que será recebida pelo consumer e repassada à integradora, que não precisará se preocupar em fazer esse tipo de tratamento, apenas aplicar outras regras de negócio e fazer a persistência dos dados.

Isso será possível pois o Jackson (biblioteca padrão para serialização e deserialização de objetos java para json) utiliza o ObjectMapper para serializar objetos. Assim, podemos adicionar ao object mapper um módulo personalizado para serializar tipos específicos de dados, no caso, strings.

Cenário Base:

Vamos utilizar como base da aplicação uma pequena api que recebe no seu request dados de estudantes e então envia um response com esses mesmos dados. Não vamos focar em regras de negócio, persistências, comunicações com mensageria pois não é nosso foco, vamos falar diretamente sobre a serialização customizada de objetos.

Classe Controller:

Ela recebe um request no endpoint POST /students e retorna um status OK com o objeto serializado (Uma String). Essa string retornada do service é resultado da serialização feita pelo object mapper que você verá na implementação da classe de service.
Como não vamos implementar a mensageria aqui, essa String retornada do endpoint irá representar o JSON enviado à mensageria.

@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

Classe de Request:

@Data
public class StudentRequest {

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

}
Enter fullscreen mode Exit fullscreen mode

Classe Service:

@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

Essa implementação como está gera o seguinte resultado:

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

Vamos, agora, à nossa implementação:

Implementando o Serializador Customizado:

Vamos começar criando uma classe que será responsável por sobrescrever o comportamento padrão de serialização de objetos string do object mapper. Vamos chamar essa classe de 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

Perceba que ela extende a classe JsonSerializer passando dentro do operador diamante ( <> ) o tipo do objeto que queremos implementar nosso serializador, no caso, String.

Então sobrescrevemos o método serialize, que recebe a String a ser serializada, um JsonGenerator e um SerializerProvider.
Fazemos, então, uma verificação de campos nulo ou vazio e, caso não seja nulo ou vazio, o jsonGenerator irá escrever a String (método writeString) recebendo nossa string (s) com o método toUpperCase(), ou seja, ele irá escrever nossa String no valor de cada campo string como uppercase.

Se estiver vazio ou nulo, ele irá escrever nulo.

Certo, agora que escrevemos nossa classe de Serialização customizada é só rodar o programa? Ainda não, pois temos que avisar o object mapper que ele deve utilizar essa classe. Faremos isso lá no nosso service, logo após instanciar o object mapper.

Classe StudentServiceImpl:

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

Perceba que logo após inicializarmos o objectmapper nós adicionamos um novo módulo a ele, apontando para nosso serializador de upper case.

Feito isso, colocamos para retornar esse json criado e o controller vai nos mostrar no response do endpoint.

Vamos ver o resultado!

Resultado:

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

Funcionou perfeitamente bem para o que propomos.
Lembrando que você pode criar serializador personalizado para outros tipos de dados também, como você preferir! E que essa não é a única forma de fazer e nem sei se é a melhor, mas foi a que me atendeu quando precisei.

Por enquanto é isso, caso você tenha sugestões, dúvidas ou comentários, é só comentar ou me chamar.
Caso você queira ver o repositório deste código, ele se encontra aqui.

Esse repositório tem o código base na branch main, uma branch implementando o nosso serializador que vimos nesse artigo e também uma branch com a implementação de um deserializador que vimos no artigo anterior.

Espero que tenha gostado e que tenha sido útil pra você.

💖 💪 🙅 🚩
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