Configurando e customizando a serialização e desserialização de Objetos Java com Spring Boot

Por Gaspar Barancelli Junior em 22 de fevereiro de 2023

Em ciência da computação, no contexto de armazenamento e transmissão de dados, serialização é o processo de tradução de estruturas de dados ou estado de objeto em um formato que possa ser armazenado e reconstruído posteriormente no mesmo ou em outro ambiente computacional.

O Spring Boot por padrão utiliza a biblioteca Jackson para serialização e desserialização de objetos Java para Json. Tanto o Spring como biblioteca Jackson nos permite configurar módulos para customizar toda e qualquer serialização e desserialização de nossos objetos.

Como exemplo vamos implementar uma serialização e desserialização para objetos do tipo LocalDate.

Primeiramente vamos criar uma classe responsável pela serialização, vamos chama-la de LocalDateSerializer, ela deve extender JsonSerializer e passar como tipagem o objeto LocalDate. Ao extender a classe LocalDateSerializer somos obrigados a implementar o método serialize, nesse método devemos ser capazes de transformar um objeto Java em uma String, como a tipagem informada é LocalDate, receberemos um objeto desse tipo por parâmetro, então vamos formata-lo para o padrão ISO_LOCAL_DATE (yyyy-MM-dd);

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

import java.io.IOException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

public class LocalDateSerializer extends JsonSerializer<LocalDate> {

    @Override
    public void serialize(LocalDate value, JsonGenerator generator, SerializerProvider serializers) throws IOException {
        String formattedDate = value.format(DateTimeFormatter.ISO_LOCAL_DATE);
        generator.writeString(formattedDate);
    }

}

Nessa segunda etapa vamos criar uma classe responsavel pela desserialização, então vamos chamala de LocalDateDeserializer, esta classe vai extender JsonDeserializer e passar como tipagem o objeto LocalDate. Ao extender a classe JsonDeserializer somos obrigados a implementar o método deserialize, nesse método devemos ser capazes de transformar uma String em um Objeto Java, como a tipagem informada é LocalDate, devemos converter uma String em LocalDate, para isso vamos dar um parse no objeto LocalDate passando por parâmetro o valor recebido em String e o formato ISO_LOCAL_DATE (yyyy-MM-dd);

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;

import java.io.IOException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

public class LocalDateDeserializer extends JsonDeserializer<LocalDate> {

    @Override
    public LocalDate deserialize(JsonParser parser, DeserializationContext context) throws IOException {
        String value = parser.getValueAsString();
        return LocalDate.parse(value, DateTimeFormatter.ISO_LOCAL_DATE);
    }

}

Em nossa última etapa vamos configurar o Jackson e também o Spring, para que eles facam uso dos nossos novos conversores. Para isso vamos criar uma classe chamada WebConfig que implementa WebMvcConfigurer, nessa classe vamos sobrescrever o método configureMessageConverters, este método recebe uma lista de classes responsáveis por converterem objetos dependendo do formato da saida escolhido na requisição.

Vamos olhar com mais cuidado para essa classe a seguir, vejamos que ela recebe um objeto do tipo ObjectMapper pelo construtor, esse objeto é um bean criado pelo Spring, ele fornece funcionalidades para leitura e gravação de JSON, de e para POJOs (Plain Old Java Objects). Devemos injetar esse objeto pois ele é a base para a nossa configuração.

Agora observe a implementação do método customJackson2HttpJsonMessageConverter, nesse método criamos um novo modulo Jackson, e nesse modulo adicionamos nossas classes criadas anteriormente, depois registramos o modulo no ObjectMapper e por fim instanciámos um objeto do tipo MappingJackson2HttpMessageConverter atribuindo o ObjectMapper alterado por parâmetro.

No método configureMessageConverters que sobrescrevemos, retornamos uma lista de conversores, além do nosso objeto MappingJackson2HttpMessageConverter contendo nossas classes de serialização e desserialização, também retornamos StringHttpMessageConverter que é responsável por tratar apenas Strings e também FormHttpMessageConverter que é responsável por tratar a conversão de conteúdos HTML, esses últimos conversores são necessariamente precisam estar configuramos, fica como um bônus.

import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.time.LocalDate;
import java.util.List;

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    private final ObjectMapper objectMapper;

    public WebConfig(ObjectMapper objectMapper) {
		this.objectMapper = objectMapper;
	}

    private MappingJackson2HttpMessageConverter customJackson2HttpJsonMessageConverter() {
        SimpleModule module = new SimpleModule("blog", Version.unknownVersion());
		module.addSerializer(LocalDate.class, new LocalDateSerializer());
		module.addDeserializer(LocalDate.class, new LocalDateDeserializer());
		objectMapper.registerModule(module);

		MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
		converter.setObjectMapper(objectMapper);
		return converter;
	}

    @Override
	public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
		converters.add(new StringHttpMessageConverter());
		converters.add(customJackson2HttpJsonMessageConverter());
		converters.add(new FormHttpMessageConverter());
	}

}

Agora toda requisição que tenha objetos do tipo LocalDate sendo trafegados, e que o content-type for definido como application/json, os conversores do Jackson passaram a chamar nossas implementações.

// Livros recomendados relacionados ao assunto do post

// Compartilhe esse Post