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
- Requer que as classes Java sejam escritas respeitando certas regras, caso contrário algumas exceções podem ser lançadas.
- Está presente em diversos projetos muito utilizados pela comunidade Java, como o Hibernate e o Spring Framework.
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'
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>
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.
- Declarar um campo para cada propriedades do JSON;
- Usar a anotação @JsonIgnoreProperties ou @JsonIgnore para as propriedades que não desejamos utilizar;
- Declarar métodos assessores (getters) públicos;
- Garantir que pelo menos um construtor sem argumentos estará presente.
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"
}
...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;
}
}
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);
}
}
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
}
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
}
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
}
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"
}
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
}
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
}
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:
- Seus campos e/ou métodos acessores (getters) devem ser públicos
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
}
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);
}
}
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
}
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"
}
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>>() {});
}
}
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);
}
}
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"
}
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>>() {});
}
}
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.