[Java SpringBoot] Como Criar Deserializador Personalizado para seus Requests

brunbs

Bruno Barbosa

Posted on November 3, 2023

[Java SpringBoot] Como Criar Deserializador Personalizado para seus Requests

Introdução

Esses dias surgiu uma situação a ser resolvida no trabalho e resolvi compartilhar a solução aqui no dev.to!

Vou tentar resumir o problema o máximo que eu conseguir:
Tínhamos uma API em Java SpringBoot que persistia informação em um banco de dados PostgreSQL.
Essa API recebia um objeto no request que era composto por cerca de 200 campos, a maioria deles eram strings.

Um tempo após o desenvolvimento ter sido concluído, surgiu a necessidade de todas as informações textuais serem persistidas em upper case (TIPO ASSIM, COM CAPS LOCK).

O time, então, se reuniu para decidir qual abordagem seguiríamos para modificar a api em questão.
A primeira alternativa proposta era receber todos os campos e, manualmente, utilizar o método toUpperCase() da classe String para converter atributo por atributo para upper case.

Como a API tinha certa complexidade, essa solução poderia acabar levando muito tempo e comprometendo a entrega da squad.

Como trabalhamos com prazos apertados, pensei em uma outra abordagem para diminuir o custo de tempo da solução: fazer um deserializador personalizado para que, todos os campos string sejam convertidos em upper case na chegada do request, ao ser mapeado para um objeto na entrada do endpoint da API.

Esse desenvolvimento levou cerca de 30 minutos para ser concluído, entre pesquisa e desenvolvimento, economizando um bom tempo de trabalho na equipe e funcionou muito bem.

Vou compartilhar com vocês o que foi desenvolvido utilizando uma pequena API de exemplo de caso de uso. Gostaria de deixar claro que o objetivo aqui não é se esta é a melhor prática (isso você decide com seu time de acordo com suas necessidades e disponibilidades), também não me preocupei em fazer o código mais a prova de falhas aqui na demonstração, então não coloquei, por exemplo, try catch pois este não é o foco deste artigo.

Para quem quiser ver o **repositório **do código utilizado, aqui está o link:
Repositório GitHub

No repositório você irá encontrar a branch main com o código base do projeto antes da implementação.
Encontrará, também, a branch onde foi implementado o deserializador customizado e, também, uma branch onde implementei um serializador customizado caso você queira que a aplicação seja na saída, na hora de montar o JSON que sai da sua aplicação.
(Vou falar sobre o serializador no próximo artigo!)

Apresentando a aplicação:

Para exemplificarmos, criei uma pequena aplicação que possui como porta de entrada o endpoint POST /students, que espera receber no corpo da requisição um JSON que será mapeado para um objeto da classe StudentRequest. Segue abaixo o código da classe StudentController.java

@RestController
public class StudentController {

    @Autowired
    private StudentService studentService;

    @PostMapping("/students")
    ResponseEntity<StudentResponse> createStudent(@RequestBody StudentRequest studentRequest) {
        return ResponseEntity.ok().body(studentService.createStudent(studentRequest));
    }

}
Enter fullscreen mode Exit fullscreen mode

Podemos ver que esta classe está recebendo injeção do service que irá tratar das regras de negócio.

A nossa classe de Request está desta maneira (StudentRequest.java):

@Data
public class StudentRequest {

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

}

Enter fullscreen mode Exit fullscreen mode

O response tem os mesmos campos, porém com nome de StudentResponse.

Este service, o StudentService.java terá as regras de negócio para depois persistir as informações do Student no banco de dados. Não me importei em implementar a persistência no banco pois não é o foco aqui, mas basicamente teríamos um service desta forma:

@Service
public class StudentServiceImpl implements StudentService {

    @Autowired
    private StudentMapper studentMapper;

    @Override
    public StudentResponse createStudent(StudentRequest studentRequest) {

        //some business logics

        return studentMapper.requestToResponse(studentRequest);
    }
}
Enter fullscreen mode Exit fullscreen mode

Perceba que aqui, para encurtar transformações da classe de request para a classe de entidade ou response estou utilizando o MapStruct (escreverei sobre o MapStruct em outro artigo).

Ao rodar o programa e chamar o endpoint, temos o seguinte resultado:

Postman imagem of the request with text field in lower case being returned as they were received, with no modifications

Certo, o código escrito desta forma (que vocês podem ver na branch main do repositório) acaba persistindo as informações como elas chegam no request, sem nenhum tratamento ou conversão dos campos em upper case como queremos. Vamos, então, à implementação do nosso Deserializador, para que os campos String passem pelo toUpperCase() logo na entrada da requisição.

Implementando o Deserializador:

Essa solução é incrivelmente simples, vamos apenas criar uma classe chamada ToUpperCaseConverter.java e faremos ela extender a classe StdConverter do Jackson.
Dentro do operador diamante ( <> ) colocaremos dois tipos de objeto, um de entrada (IN) e outro de saída (OUT), ou seja, o StdConverter tem como "assinatura", StdConverter. Como vamos receber uma String e devolver uma String porém em upper case, vamos utilizar StdConverter.

E então vamos sobrescrever o método convert(String value) colocando nosso toUpperCase() na implementação deste método. Ficando assim nossa classe:

import com.fasterxml.jackson.databind.util.StdConverter;

public class ToUpperCaseConverter extends StdConverter<String, String> {

    @Override
    public String convert(String value) {
        return value.toUpperCase();
    }

}
Enter fullscreen mode Exit fullscreen mode

Essa classe, para fins de organização, coloquei dentro de um pacote chamado utils.

Agora, como fazemos para marcar os campos que precisam passar por essa conversão? Para isso basta irmos na nossa classe de request e adicionar a anotação @JsonDeserialize e passar para ela qual classe que irá aplicar a deserialização customizada que criamos, ou seja, a classe ToUpperCaseConverter.java. Vamos adicionar essa anotação em todos os campos que precisamos fazer essa conversão, ficando, assim, nossa classe de Request:

@Data
public class StudentRequest {

    private Long registration;
    @JsonDeserialize(converter = ToUpperCaseConverter.class)
    private String name;
    @JsonDeserialize(converter = ToUpperCaseConverter.class)
    private String lastName;

}

Enter fullscreen mode Exit fullscreen mode

Agora, podemos colocar nossa API para rodar e vermos o resultado, como mostrado no print do POSTMAN abaixo:

Imagem da aplicação Postman mostrando uma chamada com campos string em lower case e o response com os campos em upper case após conversão

Você pode fazer muitas adaptações para esta lógica inclusive criando um Deserializador customizado para toda a classe de Request! Passando campo a campo instruindo que manipulação você quer fazer com o campo, mas isso fica para outra postagem.

Espero que tenha conseguido contribuir e dúvidas e sugestões podem ser enviadas aqui, fico muito grato caso tenham melhorias a sugerir.

💖 💪 🙅 🚩
brunbs
Bruno Barbosa

Posted on November 3, 2023

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

Sign up to receive the latest update from our blog.

Related