Introdução ao Jackson ObjectMapper

Neste artigo conheceremos a classe ObjectMapper, muito utilizada para converter classes Java em JSON, e vice-versa, na comunicação entre APIs RESTful e SPAs.

O que é?

ObjectMapper é uma classe da biblioteca Jackson. Ela pode transformar classes POJO (Plain Old Java Objects) em JSON (JavaScript Object Notation) e vice-versa.

Por ser mais leve que seu antecessor, o XML, e nativo em linguagem populares como o JavaScript, JSON se tornou o formato de dado mais utilizado na comunicação de aplicações hoje em dia, especialmente entre APIs RESTful e clientes web/mobile.

Por que aprender sobre isso?

Uma vez que JSON é o padrão para comunicação back-end/front-end hoje em dia, é fundamental aprender como processar esse formato de texto em Java.

Por exemplo, a falta desse conhecimento pode impedir o programador de conseguir receber no backend as informações enviadas por um formulário.

Características

Instalação e configuração

Para utilizar essa classe você precisa instalar a dependência jackson-databind em seu projeto. Há duas formas de fazer isso.

Em projetos Gradle configure a sessão de dependências do arquivo build.grade como mostra o Código 1.

implementation 'com.fasterxml.jackson.core:jackson-databind:2.11.1'
Código 1. Configurando a dependência jackson-databind no arquivo build.grade

Em projetos Maven configure a sessão de dependências do arquivo pom.xml como mostra o Código 2.

<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.11.1</version> </dependency>
Código 2. Configurando a dependência jackson-databind no arquivo pom.xml

Antes de configurar essas dependências, consulte a versão mais recente da biblioteca jackson-databind no repositório oficial.

O que saber antes de começar?

Geralmente, será um framework quem utilizará a classe ObjectMapper. Por isso, na maioria das vezes precisaremos apenas fazer um de-para do JSON para uma classe Java e vice-versa.

Quanto a classe Java que será escrita, ela deve cumprir certos pré-requisitos, listados abaixo, para que a transformação não falhe.

A seguir, veremos exemplos que exploram esses pré-requisitos e os erros causados pela ausência deles.

Como transformar JSON em objetos Java

Esse é um cenário de leitura, também chamado de unmarshal/deserialização, onde recebemos um JSON e desejamos ler/transformar ele em uma classe Java.

Por exemplo, para o JSON do Código 3.

{ "nome":"Maria da Silva" }
Código 3. Exemplo de um JSON

...precisamos de uma classe como a que pode ser vista no Código 4.

package br.com.devmedia; public class Pessoa { private String nome; public String getNome() { return nome; } public void setNome(final String nome) { this.nome = nome; } }
Código 4. Exemplo de uma classe em Java

Para testar se nosso modelo atende aos pré-requisitos podemos fazer a transformação usando ObjectMapper da seguinte forma como é exibido no Código 5.

package br.com.devmedia; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; public class Main { public static void main(final String[] args) throws JsonProcessingException { final var json = "{\"nome\":\"Maria da Silva\"}"; final var objectMapper = new ObjectMapper(); final var pessoa = objectMapper.readValue(json, Pessoa.class); System.out.println(pessoa); } }
Código 5. Utilizando o ObjectMapper para testar o modelo

Explicando o código:

Linha 8: Armazenamos o JSON em uma String chamada json.

Linha 9: Criamos uma instância de ObjectMapper de forma bem trivial.

Linha 10: O método readValue recebe o JSON, json, e a classe que ele representa, Pessoa.class, e retorna uma instância dessa classe corretamente iniciada.

Como resultado, a variável pessoa deve ser exatamente Pessoa{nome='Maria da Silva'}.

Default Constructor

Antes de avançarmos, lembre-se que uma classe que não declara um construtor recebe do compilador um construtor padrão sem argumentos. Quando declaramos um construtor para a classe isso não acontece.

Por exemplo, a classe do Código 6 essa classe atende ao pré-requisito, pois possui um construtor sem argumento, aquele autogerado pelo compilador, uma vez que nela não declaramos nenhum construtor.

public class Pessoa { private String nome; // Código restante omitido }
Código 6. Classe que possui um construtor autogerado

Porém, essa classe não atende ao pré-requisito, porque ela declara um construtor com argumentos e por isso nenhum construtor padrão será gerado automaticamente pelo compilador, como podemos ver no Código 7.

public class Pessoa { private String nome; public Pessoa(String nome) { this.nome = nome; } // Código restante omitido }
Código 7. Exemplo de classe que possui um construtor

Uma exceção do tipo MismatchedInputException será lançada quando o ObjectMapper tentar usá-la.

Para corrigir esse erro, remova o construtor da classe ou declare pelo menos dois construtores, sendo um deles sem argumentos como foi feito no Código 8.

public class Pessoa { private String nome; public Pessoa() { } public Pessoa(final String nome) { this.nome = nome; } // Código restante omitido }
Código 8. Classe que possui dois construtores

Dessa forma, o código voltará a funcionar.

Ignorando propriedades desconhecidas

Quando o ObjectMapper encontra uma propriedade que não corresponde a nenhum campo da classe ele lança uma exceção do tipo UnrecognizedPropertyException.

Por exemplo, se usarmos a classe Pessoa, vista anteriormente, no processamento do JSON do Código 9 a operação deve falhar, uma vez que idade não está presente nessa classe.

{ "nome":"Maria da Silva", "idade":"18" }
Código 9. Exemplo de JSON

Nesse contexto, podemos reescrever a classe Pessoa (Código 10), adicionando idade, respeitando os pré-requisitos para campos e métodos assessores vistos anteriormente.

public class Pessoa { private String nome; private Integer idade; // Código restante omitido }
Código 10. Reescrevendo a classe Pessoa

Porém, algumas vezes o JSON a ser lido possui propriedades que não desejamos utilizar. Nesses casos, precisamos avisar ao ObjectMapper que elas devem ser ignoradas. Isso é feito usando a anotação @JsonIgnoreProperties como pode ser visto no Código 11.

public class Pessoa { @JsonIgnoreProperties(ignoreUnknown = true) public class Pessoa { private String nome; // Código restante omitido }
Código 11. Utilizando a anotação @JsonIgnoreProperties

Como se pode notar, essa anotação, apesar de prática, deve ser utilizada com cuidado, uma vez que ela aumenta a distância entre a representação do dado na origem e no back-end.

Como transformar objetos Java em JSON

Nesse exemplo, transformamos uma instância da classe Pessoa em um JSON. Esse é um contexto de escrita ou marshal/serialização.

Para isso, a classe Pessoa também deve cumprir com o seguinte pré-requisito:

Uma exceção do tipo InvalidDefinitionException será lançada caso esse pré-requisito não seja cumprido.

No Código 12 destacamos essa característica na classe Pessoa.

package br.com.devmedia; public class Pessoa { private String nome; public String getNome() { return nome; } // Código restante omitido }
Código 12. Classe Pessoa com os campos e/ou métodos públicos

Explicando o código:

Linhas 4 e 6: Note que a classe apresentada possui um campo privado, nome, mas o método assessor (getters) desse campo, getNome(), é público. Portanto, ela atende ao pré-requisito e nenhuma exceção será lançada ao utilizá-la.

Agora no Código 13, transformamos uma instância de Pessoa em um JSON.

package br.com.devmedia; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.Collections; public class Main { public static void main(final String[] args) throws JsonProcessingException { final var pessoa = new Pessoa(); pessoa.setNome("Maria da Silva"); final var objectMapper = new ObjectMapper(); final var json = objectMapper.writeValueAsString(pessoa); } }
Código 13. Transformando uma instância de Pessoa em um JSON

Explicando o código:

Linha 13: Instanciamos um ObjectMapper.

Linha 14: Usamos o método writeValueAsString para transformar o objeto pessoa em um JSON.

Como resultado, a variável json deve possuir o valor {"nome":"Maria da Silva"}.

Note que o método onde writeValueAsString é invocado deve tratar a exceção JsonProcessingException, que pode ser lançada caso a transformação falhe.

Ignorando campos privados

Algumas vezes, pode ser necessário que algum campo da classe não esteja presente no JSON gerado a partir dela. Isso pode ser feito anotando o campo com @JsonIgnore.

Considere que temos na classe Pessoa um novo campo chamado CPF, mas não desejamos passar esse dado adiante. A classe ficaria conforme o Código 14.

public class Pessoa { private String nome; @JsonIgnore private String cpf; // Código restante omitido }
Código 14. Utilizando a anotação @JsonIgnore

Dessa forma, o JSON gerado a partir dela não conteria a propriedade cpf, uma vez que o campo homônimo está anotado com @JsonIgnore.

Como transformar um array de objetos JSON na classe List

Dessa vez, o JSON de exemplo do Código 15 possui um array de objetos.

{ "nome": "Maria da Silva" }, { "nome": "José da Silva" }
Código 15. JSON que possui um array de objetos

Para transformar esse JSON em uma lista de objetos Java, usando List, precisamos do auxílio da classe TypeReference, também da biblioteca Jackson.

O código para essa conversão pode ser visto no Código 16.

package br.com.devmedia; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.List; public class Main { public static void main(final String[] args) throws JsonProcessingException { final var json = "[{\"nome\":\"Maria da Silva\"},{\"nome\":\"José da Silva\"}]"; final var objectMapper = new ObjectMapper(); final var pessoas = objectMapper.readValue(json, new TypeReference<List<Pessoa>>() {}); } }
Código 16. Transformando JSON em uma lista de objetos Java

Explicando o código:

Linha 13: Aqui usamos o método readValue, explicado anteriormente, mas dessa vez o segundo argumento é uma instância de TypeReference. Note que entre os símbolos < e > passamos como parâmetro o tipo desejado, List<Pessoa>.

Como resultado, a variável pessoas deve conter o valor [Pessoa{nome='Maria da Silva'}, Pessoa{nome='José da Silva'}], ou seja, ela é uma lista que contém duas instâncias de Pessoa.

Como transformar um array de objetos JSON em um array de objetos Java

Podemos ainda transformar um JSON em um array, caso não compense criar uma classe para representá-lo. Nesse caso, a Linha 13 do exemplo anterior ficaria como pode ser visto no Código 17.

package br.com.devmedia; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.List; public class Main { public static void main(final String[] args) throws JsonProcessingException { final var json = "[{\"nome\":\"Maria da Silva\"},{\"nome\":\"José da Silva\"}]"; final var objectMapper = new ObjectMapper(); final var pessoas = objectMapper.readValue(json, Pessoa[].class); } }
Código 17. Transformando um JSON em um array Java

Note que dessa vez não usamos TypeReference.

Como resultado, a variável pessoas será um Array, contendo duas instâncias da classe Pessoa, iniciadas com os valores definidos no JSON.

Como transformar um JSON em uma classe Map

Caso não tenhamos um POJO para representar um JSON, podemos mapeá-lo para uma classe Map como alternativa.

Map é uma estrutura de chave/valor e JSON de propriedade/valor. Assim, cada propriedade no JSON será uma chave no Map e o valor de cada propriedade no JSON será o valor da cada chave no Map. Comumente usamos Map<String, String> nesses casos, onde a primeira String entre < e > corresponde ao tipo da chave e a segunda ao do valor.

Considere o JSON do Código 18.

{ "nome": "Maria da Silva" }
Código 18. Exemplo de JSON

Para lê-lo podemos usar o Código 19.

package br.com.devmedia; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.Map; public class Main { public static void main(final String[] args) throws JsonProcessingException { final var json = "{\"nome\":\"Maria da Silva\"}"; final var objectMapper = new ObjectMapper(); final var pessoa = objectMapper.readValue(json, new TypeReference<Map<String, String>>() {}); } }
Código 19. Lendo um JSON utilizando uma classe Map

Explicando o código:

Linha 13: Novamente utilizamos TypeReference para comunicar o tipo retornado por readValue, Map<String, String>.

Após a execução desse método, a classe pessoa deve corresponder a {nome=Maria da Silva}. No mapa, a chave nome contém o valor Maria da Silva.

Conclusão

Jackson é uma excelente biblioteca para lidar com a transformação de texto em código. Ela suporta não só JSON, mas também YAML, XML entre outros formatos. Dominá-la pode ser um diferencial para o programador, principalmente para a criação de filtros que processem dados em APIs. Geralmente, eles são executados antes dos controllers e transformam dados, leem JWTs e fazem outras tarefas de mais baixo nível.

Para futuras pesquisas, a conversão de algo em JSON é frequentemente chamada de marshall, ao contrário de unmarshal. A comunidade Java costuma usar os termos Serialization (serialização) e Deserialization (deserialização) para as mesmas operações sobre POJOs.

Artigos relacionados