Configurando e customizando a serialização e desserialização de Objetos Java com Spring Boot
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.