Mapeando um Objeto JSON Dinâmico com Jackson
1. Introdução
Trabalhar com estruturas de dados JSON predefinidas com Jackson é simples. No entanto, às vezes precisamos lidar comJSON objects, which have unknown properties dinâmico.
Neste breve tutorial, veremos várias maneiras de mapear objetos JSON dinâmicos em classes Java.
Observe que em todos os testes, assumimos que temos um campoobjectMapper do tipocom.fasterxml.jackson.databind.ObjectMapper.
Leitura adicional:
Mapeando valores aninhados com Jackson
Aprenda três maneiras de desserializar valores JSON aninhados em Java usando a biblioteca Jackson.
Usando opcional com Jackson
Uma rápida visão geral de como podemos usar o opcional com Jackson.
2. UsandoJsonNode
Digamos que queremos processar as especificações do produto em uma loja virtual. All products have some common properties, but there’re others, which depend on the type of the product.
Por exemplo, queremos saber a proporção da tela de um telefone celular, mas essa propriedade não faz muito sentido para um sapato.
A estrutura de dados fica assim:
{
"name": "Pear yPhone 72",
"category": "cellphone",
"details": {
"displayAspectRatio": "97:3",
"audioConnector": "none"
}
}
Armazenamos as propriedades dinâmicas no objetodetails.
Podemos mapear as propriedades comuns com a seguinte classe Java:
class Product {
String name;
String category;
// standard getters and setters
}
Além disso, precisamos de uma representação apropriada para o objetodetails. Por exemplo,com.fasterxml.jackson.databind.JsonNode can handle dynamic keys.
Para usá-lo, temos que adicioná-lo como um campo à nossa classeProduct:
class Product {
// common fields
JsonNode details;
// standard getters and setters
}
Por fim, verificamos que funciona:
String json = "";
Product product = objectMapper.readValue(json, Product.class);
assertThat(product.getName()).isEqualTo("Pear yPhone 72");
assertThat(product.getDetails().get("audioConnector").asText()).isEqualTo("none");
No entanto, temos um problema com esta solução. Our class depends on the Jackson library since we have a JsonNode field.
3. UsandoMap
Podemos resolver esse problema usandojava.util.Map para o campodetails. Mais precisamente, temos que usarMap<String, Object>.
Tudo o resto pode permanecer o mesmo:
class Product {
// common fields
Map details;
// standard getters and setters
}
E então podemos verificá-lo com um teste:
String json = "";
Product product = objectMapper.readValue(json, Product.class);
assertThat(product.getName()).isEqualTo("Pear yPhone 72");
assertThat(product.getDetails().get("audioConnector")).isEqualTo("none");
4. Usando@JsonAnySetter
As soluções anteriores são boas quando um objeto contém apenas propriedades dinâmicas. No entanto, às vezes temosfixed and dynamic properties mixed in a single JSON object.
Por exemplo, talvez seja necessário achatar nossa representação do produto:
{
"name": "Pear yPhone 72",
"category": "cellphone",
"displayAspectRatio": "97:3",
"audioConnector": "none"
}
Podemos tratar uma estrutura como esta como um objeto dinâmico. Infelizmente, isso significa que não podemos definir propriedades comuns - temos que tratá-las dinamicamente também.
Alternativamente, podemos usar@JsonAnySetter to mark a method for handling additional, unknown properties. Esse método deve aceitar dois argumentos: o nome e o valor da propriedade:
class Product {
// common fields
Map details = new LinkedHashMap<>();
@JsonAnySetter
void setDetail(String key, Object value) {
details.put(key, value);
}
// standard getters and setters
}
Observe que temos que instanciar o objetodetails para evitarNullPointerExceptions.
Como armazenamos as propriedades dinâmicas emMap, podemos usá-lo da mesma maneira que fizemos antes:
String json = "";
Product product = objectMapper.readValue(json, Product.class);
assertThat(product.getName()).isEqualTo("Pear yPhone 72");
assertThat(product.getDetails().get("audioConnector")).isEqualTo("none");
5. Criando um desserializador personalizado
Na maioria dos casos, essas soluções funcionam bem. No entanto, às vezes precisamos de muito mais controle. Por exemplo, podemos armazenar informações de desserialização sobre nossos objetos JSON em um banco de dados.
Podemos direcionar essas situações com um desserializador personalizado. Por ser um tópico complexo, nós o cobriremos em um artigo diferente,getting Started with Custom Deserialization in Jackson.
6. Conclusão
Neste artigo, vimos várias maneiras de manipular objetos JSON dinâmicos com Jackson.
Como de costume, os exemplos estão disponíveisover on GitHub.