Logo

dev-resources.site

for different kinds of informations.

[Java SpringBoot] Como Criar Deserializador Personalizado para seus Requests

Published at
11/3/2023
Categories
java
springboot
programming
jackson
Author
brunbs
Author
6 person written this
brunbs
open
[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.

jackson Article's
30 articles in total
Favicon
Why Do We Still Need Jackson or Gson in Java?
Favicon
A simple GeoJSON serializer for Jackson
Favicon
[Java Spring Boot] Como Criar Serializador Personalizado para seus Responses ou Json de saída
Favicon
[Java Spring Boot] How to implement a Custom Serializer for your Responses or Json
Favicon
[Java SpringBoot] Como Criar Deserializador Personalizado para seus Requests
Favicon
[Java SpringBoot] How to implement a Custom Deserializer for your Requests
Favicon
Java Jackson JSON: How to Handle Custom Keys?
Favicon
Create a custom Jackson JsonSerializer und JsonDeserializer for mapping values
Favicon
Anotación @JsonUnwrapped
Favicon
A tale of fixing a tiny OpenAPI bug
Favicon
Kotlin Springboot -- Part 21 任意の key value の json を POST する API E2E を書く
Favicon
Formatting json Date/LocalDateTime/LocalDate in Spring Boot
Favicon
Jackson's @JsonView with SpringBoot Tutorial
Favicon
Jackson JSON parsing top-level map into records
Favicon
Using Jackson Subtypes to Write Better Code
Favicon
Java – Convert Excel File to/from JSON (String/File) – using Apache Poi + Jackson
Favicon
How to resolve Json Infinite Recursion problem when working with Jackson
Favicon
Java – Convert Excel File to/from JSON (String/File) – using Apache Poi + Jackson
Favicon
Practical Java 16 - Using Jackson to serialize Records
Favicon
Kotlin – Convert Object to/from JSON with Jackson 2.x
Favicon
💾 Java Records 💿 with Jackson 2.12
Favicon
Jackson, JSON and the Proper Handling of Unknown Fields in APIs
Favicon
Polymorphic deserialization with Jackson and no annotations
Favicon
Playing around with Kotlin Sealed Classes
Favicon
Moonwlker: JSON without annotation
Favicon
Jackson Readonly properties and swagger UI
Favicon
Registering Jackson sub-types at runtime in Kotlin
Favicon
Parsing JSON in Spring Boot, part 1
Favicon
Customize how Jackson does LocalDate Parsing
Favicon
Painless JSON with Kotlin and jackson

Featured ones: