Mapeando um Objeto JSON Dinâmico com Jackson

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.

Read more

Usando opcional com Jackson

Uma rápida visão geral de como podemos usar o opcional com Jackson.

Read more

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.