Jackson vs Gson: A Pedra de Roseta da Serialização no Desenvolvimento Web com Java
Em 1799, durante a campanha de Napoleão Bonaparte no Egito, um grupo de soldados franceses fez uma descoberta que mudaria para sempre o estudo da civilização egípcia: um bloco de granito negro, parcialmente quebrado, mas com um segredo poderoso. Gravado nele, havia um mesmo decreto escrito em três sistemas de escrita distintos - hieróglifo, demótico e grego antigo. Esse artefato, que ficaria conhecido como a Pedra de Roseta, tornou-se a chave para decifrar os mistérios do Egito Antigo, pois os estudiosos da época já compreendiam o grego e, a partir dele, conseguiram traduzir os hieróglifos, um enigma que havia permanecido indecifrável por séculos.
Ao visitar o Museu Britânico e ver a Pedra de Roseta de perto, me impressionei com o impacto que esse objeto teve na história da linguagem. No fundo, a pedra representava a ponte entre três mundos - um intermediário que permitiu que línguas distintas se comunicassem.
Curiosamente, esse acontecimento me remeteu a um desafio semelhante no desenvolvimento web com Java. Aplicações modernas trocam informações constantemente, e um dos formatos mais comuns para isso é o JSON. Nesse ponto, para que um objeto Java possa ser compreendido por uma API, banco de dados, ou outros sistema, ele precisa ser convertido para JSON, e depois reconvertido (desserializado) de volta para um objeto Java. É aqui que entram Jackson e Gson - ferramentas que, assim como a Pedra de Roseta, permitem essa tradução entre diferentes linguagens, possibilitando a comunicação entre sistemas de maneira eficiente.
Nesse artigo, exploraremos como Jackson e Gson desempenham esse papel, comparando suas vantagens e desvantagens para aplicações web. Assim como a Pedra de Roseta ajudou a decifrar um idioma perdido, essas bibliotecas nos ajudam a interpretar e manipular dados no desenvolvimento web com Java. Vamos explorar isso juntos.
O que é serialização e desserialização?
Java é uma linguagem fortemente orientada a objetos, onde os dados são representados por classes e objetos. No contexto de um sistema bancário, por exemplo, podemos ter uma classe que representa uma transação financeira, outra que armazena os dados de um cliente e assim por diante. Dentro do ambiente Java, esses objetos são facilmente compreendidos e manipulados.
Porém, o problema surge quando precisamos compartilhar essas informações com outros sistemas, como uma API ou um banco de dados. Diferentes tecnologias podem ter diferentes formas de entender e armazenar dados.
É aí que entra a serialização: um processo que converte objetos Java em um formato que pode ser facilmente armazenado ou transmitido, como JSON (JavaScript Object Notation) ou XML.
Por outro lado, a desserialização faz o caminho inverso: transforma um JSON de volta em um objeto Java utilizável dentro da aplicação.
Isso é essencial no desenvolvimento web, onde frequentemente precisamos trocar dados entre servidores e clientes de forma eficiente. E é exatamente aí que bibliotecas como Jackson e Gson entram em cena.
Jackson vs Gson: O embate das ferramentas
Jackson e Gson destacam-se como duas das principais bibliotecas para serialização e desserialização de JSON em Java. A seguir, comparamos essas ferramentas em termos de casos de uso, velocidade, uso de memória e facilidade de uso.
Casos de Uso:
- Jackson: Ideal para aplicações que requerem alta performance e lidam com grandes volumes de dados, como sistemas corporativos de larga escala.
- Gson: Recomendado para projetos mais simples ou de menor escala, onde a facilidade e rapidez de implementação são prioritárias.
Velocidade de Serialização e Desserialização:
- Jackson: Reconhecido por sua alta velocidade e otimização, especialmente em cenários que demandam alto desempenho.
- Gson: Embora seja eficiente, pode apresentar desempenho inferior ao Jackson ao lidar com estruturas JSON mais complexas ou volumosas.
Uso de Memória:
- Jackson: Projetado para ser eficiente no uso de memória, sendo inclusive adequado para operações de streaming e manipulação de grandes volumes de dados JSON.
- Gson: Funciona bem com dados de menor tamanho, mas pode consumir mais memória ao processar arquivos JSON maiores.
Facilidade de Uso:
- Jackson: Oferece uma ampla gama de funcionalidades e configurações avançadas, o que pode exigir um tempo maior de aprendizado para aproveitar todo o seu potencial.
- Gson: Destaca-se pela simplicidade e facilidade de uso, permitindo uma integração rápida, especialmente útil em projetos com prazos apertados ou requisitos menos complexos.
Jackson
Para utilizar o Jackson em seus projetos, é necessário adicionar as dependências apropriadas. Abaixo, mostramos como fazer isso em projetos Maven e Gradle
- Maven
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.18.2</version>
</dependency>
- Gradle
dependencies {
implementation 'com.fasterxml.jackson.core:jackson-databind:2.18.2'
}
Gson
Paralelamente, para utilizar o Gson, esses são os comandos para adicionar tanto em projetos Maven como Gradle.
- Maven
<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.12.1</version>
</dependency>
- Gradle
dependencies {
implementation 'com.google.code.gson:gson:2.12.1'
}
Serialização e Desserialização Simples
Vamos definir uma classe Java que representará um Faraó do Egito Antigo, a qual será utilizada para conversão para JSON.
public class Farao {
private String nome;
private String dinastia;
private String periodoDeReinado;
private List<String> realizacoes;
// Construtor
public Farao(String nome, String dinastia, String periodoDeReinado, List<String> realizacoes) {
this.nome = nome;
this.dinastia = dinastia;
this.periodoDeReinado = periodoDeReinado;
this.realizacoes = realizacoes;
}
// Restante do Código ...
}
Tutancâmon decidiu utilizar Jackson
Tutancâmon foi um faraó do Egito Antigo que governou entre c. 1332 a.C. e 1323 a.C., durante a XVIII Dinastia. Apesar de seu reinado curto, ele se tornou um dos faraós mais famosos da história devido à descoberta de seu túmulo praticamente intacto no século XX. Seu governo marcou a restauração dos antigos cultos religiosos, após as reformas monoteístas de seu predecessor, Akhenaton.
Veja abaixo os exemplos com Jackson:
- Serialização com Jackson: A seguir, criamos uma instância de Faraó e a transformamos em uma string JSON.
import com.fasterxml.jackson.databind.ObjectMapper;
public class JacksonSerializationExample {
public static void main(String[] args) throws Exception {
Farao tutancamon = new Farao(
"Tutancâmon",
"XVIII Dinastia",
"c. 1 341 a.C. — c. 1 323 a.C.",
new ArrayList<>()
);
// Criar instância do ObjectMapper
ObjectMapper objectMapper = new ObjectMapper();
// Converter o objeto Faraó para JSON
String jsonString = objectMapper.writeValueAsString(tutancamon);
// Exibir o JSON resultante
System.out.println(jsonString);
}
}
Ao executar o método main vemos que ele imprimiu a JSON string correspondente ao objeto que criamos:
{
"nome": "Tutancâmon",
"dinastia": "XVIII Dinastia",
"periodoDeReinado": "c. 1 341 a.C. — c. 1 323 a.C.",
"realizacoes": []
}
- Desserialização com Jackson: De outra forma, se em uma conexão web recebemos a resposta da API em JSON String, podemos transformá-la em objeto Faraó.
Vamos partir do seguinte JSON representando um faraó do Egito, digamos que esse seja o retorno de uma requisição feita a uma API externa:
{
"nome": "Tutancâmon",
"dinastia": "XVIII Dinastia",
"periodoDeReinado": "c. 1 341 a.C - c. 1 323 a.C.",
"realizacoes": []
}
Para converter esse JSON em um objeto da classe Farao, utilizamos o objectMapper do Jackson:
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.List;
public class JacksonDeserializationExample {
public static void main(String[] args) throws Exception {
// JSON de exemplo
String faraoJson = """
{
"nome": "Tutancâmon",
"dinastia": "XVIII Dinastia",
"periodoDeReinado": "c. 1 341 a.C. - c. 1 323 a.C.",
"realizacoes": []
}
""";
// Criar ObjectMapper
ObjectMapper objectMapper = new ObjectMapper();
// Desserializar JSON para objeto Java
Farao tutancâmon = objectMapper.readValue(faraoJson, Farao.class);
// Exibir resultado
System.out.println("Nome: " + tutancamon.getNome());
System.out.println("Dinastia: " + tutancamon.getDinastia());
System.out.println("Período de Reinado: " + tutancamon.getPeriodoDeReinado());
System.out.println("Realizações: " + tutancamon.getRealizacoes());
}
}
A saída esperada ao rodar o código acima é:
A saída esperada para o código acima é:
Nome: Tutancâmon
Dinastia: XVII
Período de Reinado: c. 1 341 a.C. - c. 1 323 a.C.
Realizações: []
Ramsés II optou por utilizar Gson
Ramsés II, também conhecido como Ramsés, o Grande, foi um dos mais importantes e longevos faraós do Egito Antigo, governando entre 1279 a.C. e 1213 a.C. durante a XIX Dinastia. Ele é lembrado por suas grandiosas construções, como os templos de Abu Simbel, suas campanhas militares que expandiram o território egípcio e por assinar um dos primeiros tratados de paz registrados na história com os hititas.
- Serialização com Gson: Façamos paralelamente a mesma operação feita com Jackson para comparação.
import com.google.gson.Gson;
import java.util.ArrayList;
public class GsonSerializationExample {
public static void main(String[] args) {
// Criando um objeto Farao
Farao ramses = new Farao(
"Ramsés II",
"XIX",
"1279 a.C. – 1213 a.C.",
new ArrayList<>()
);
// Criando instância do Gson
Gson gson = new Gson();
// Convertendo o objeto para JSON
String jsonString = gson.toJson(ramses);
// Exibindo o JSON resultante
System.out.println(jsonString);
}
}
Aqui temos a saída esperada:
{
"nome": "Ramsés II",
"dinastia": "XIX",
"periodoDeReinado": "1279 a.C. - 1213 a.C.",
"realizacoes": []
}
- Desserialização com Gson
import com.google.gson.Gson;
public class GsonDeserializationExample {
public static void main(String[] args) {
// JSON de exemplo representando um faraó
String faraoJson = """
{
"nome": "Ramsés II",
"dinastia": "XIX",
"periodoDeReinado": "1279 a.C. – 1213 a.C.",
"realizacoes": ["Expansão do Egito", "Construção de Abu Simbel"]
}
""";
// Criando instância do Gson
Gson gson = new Gson();
// Desserializando JSON para objeto Java
Farao ramses = gson.fromJson(faraoJson, Farao.class);
// Exibindo os valores do objeto
System.out.println("Nome: " + ramses.getNome());
System.out.println("Dinastia: " + ramses.getDinastia());
System.out.println("Período de Reinado: " + ramses.getPeriodoDeReinado());
System.out.println("Realizações: " + ramses.getRealizacoes());
}
}
E assim seria a saída esperada:
Nome: Ramsés II
Dinastia: XIX
Período de Reinado: 1279 a.C. – 1213 a.C.
Realizações: [Expansão do Egito, Construção de Abu Simbel]
Tutancâmon vs Ramsés
Agora que já entendemos como Jackson e Gson funcionam, vamos colocá-los à prova! Quem será mais rápido ao converter objetos Java para JSON e vice-versa?
Para esse desafio, Tutancâmon representará o Jackson, enquanto Ramsés II usará Gson. Vamos medir o tempo necessário para serializar e desserializar 10.000 e em seguida 1.000.000 de objetos e descobrir quem leva a coroa da velocidade!
Preparação do experimento
Criaremos uma lista contendo faraós fictícios e mediremos o tempo de serialização e desserialização de cada biblioteca usando System.nanoTime(). A seguir, temos o código que utilizaremos:
public class SerializacaoDesempenho {
static int DEZ_MIL_FARAOS = 10000;
static int UM_MILHAO_DE_FARAOS = 1000000;
public static void main(String[] args) throws Exception {
// Criando uma lista com faraós fictícios
List<Farao> faraos = new ArrayList<>();
for (int i = 0; i < DEZ_MIL_FARAOS; i++) {
faraos.add(new Farao("Faraó " + i, "Dinastia " + (i % 20), "Ano " + (1300 - i) + " a.C.", new ArrayList<>()));
}
// Jackson - Serialização
ObjectMapper objectMapper = new ObjectMapper();
long inicioJacksonSerial = System.nanoTime();
String jsonJackson = objectMapper.writeValueAsString(faraos);
long fimJacksonSerial = System.nanoTime();
// Jackson - Desserialização
long inicioJacksonDeserial = System.nanoTime();
List<Farao> listaJackson = objectMapper.readValue(jsonJackson, objectMapper.getTypeFactory().constructCollectionType(List.class, Farao.class));
long fimJacksonDeserial = System.nanoTime();
// Gson - Serialização
Gson gson = new Gson();
long inicioGsonSerial = System.nanoTime();
String jsonGson = gson.toJson(faraos);
long fimGsonSerial = System.nanoTime();
// Gson - Desserialização
long inicioGsonDeserial = System.nanoTime();
List<Farao> listaGson = gson.fromJson(jsonGson, List.class);
long fimGsonDeserial = System.nanoTime();
// Exibir os resultados
System.out.println("=== Resultado da Competição ===");
System.out.println("Jackson - Tempo de Serialização: " + (fimJacksonSerial - inicioJacksonSerial) / 1_000_000.0 + " ms");
System.out.println("Jackson - Tempo de Desserialização: " + (fimJacksonDeserial - inicioJacksonDeserial) / 1_000_000.0 + " ms");
System.out.println("Gson - Tempo de Serialização: " + (fimGsonSerial - inicioGsonSerial) / 1_000_000.0 + " ms");
System.out.println("Gson - Tempo de Desserialização: " + (fimGsonDeserial - inicioGsonDeserial) / 1_000_000.0 + " ms");
}
}
Resultados
- Para 10.000 faraós
=== Resultado da Competição ===
Jackson - Tempo de Serialização: 227.382717 ms
Jackson - Tempo de Desserialização: 169.068361 ms
Gson - Tempo de Serialização: 117.46488 ms
Gson - Tempo de Desserialização: 66.493696 ms
Temos que o campeão foi o Ramses II representando a biblioteca Gson. Podemos ver que para o caso de JSON com esquemas simples e poucos dados temos a Gson com um ótimo desempenho.
Agora para o caso de termos 1.000.000 de faraós fictícios em nossa lista, será que Gson ainda mantém a frente? Vejamos abaixo o resultado.
- Para 1.000.000 de faraós
=== Resultado da Competição ===
Jackson - Tempo de Serialização: 1242.482118 ms
Jackson - Tempo de Desserialização: 1489.081513 ms
Gson - Tempo de Serialização: 2467.687843 ms
Gson - Tempo de Desserialização: 2169.338205 ms
E jackson agora demonstra todo o seu poder para grandes volumes de dados com um ótimo desempenho e ganhando essa nova versão da competição.
Conclusão
Assim como a Pedra de Roseta foi essencial para decifrar os mistérios do Egito Antigo, Jackson e Gson desempenham um papel crucial na comunicação entre sistemas modernos. Ambos os frameworks se destacam na conversão de objetos Java para JSON e vice-versa, mas suas diferenças se tornam evidentes dependendo do cenário.
Os testes mostraram que, para volumes menores de dados, Gson (representado por Ramsés II) leva vantagem em velocidade. No entanto, à medida que a carga aumenta, Jackson (nosso Tutancâmon) demonstra sua superioridade, lidando com grandes volumes de dados de forma mais eficiente. Isso reforça a ideia de que a escolha entre Jackson e Gson deve considerar não apenas a facilidade de uso, mas também os requisitos de desempenho do projeto.
Se sua aplicação lida com grandes quantidades de dados e precisa de alto desempenho, Jackson é uma escolha robusta e otimizada. Já se a prioridade for simplicidade e rapidez em implementações menores, Gson pode ser a melhor opção. No fim, a decisão depende do contexto, assim como os faraós escolhiam suas estratégias para garantir a grandeza de seus impérios.