Mapeando valores aninhados com Jackson

Mapeando valores aninhados com Jackson

1. Visão geral

Um caso de uso típico ao trabalhar com JSON é executar uma transformação de um modelo em outro. Por exemplo, podemos querer analisar um gráfico de objetos complexo e densamente aninhado em um modelo mais direto para uso em outro domínio.

Neste artigo rápido, veremoshow to map nested values with Jackson para nivelar uma estrutura de dados complexa. Vamos desserializar JSON de três maneiras diferentes:

  • Usando@JsonProperty

  • UsandoJsonNode

  • Usando umJsonDeserializer personalizado

Leitura adicional:

Usando opcional com Jackson

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

Read more

Herança com Jackson

Este tutorial demonstrará como lidar com a inclusão de metadados de subtipo e como ignorar propriedades herdadas das superclasses com Jackson.

Read more

Usando @JsonComponent no Spring Boot

Aprenda a usar a anotação @JsonComponent no Spring Boot.

Read more

2. Dependência do Maven

Vamos primeiro adicionar a seguinte dependência apom.xml:


    com.fasterxml.jackson.core
    jackson-databind
    2.9.4

Podemos encontrar as versões mais recentes dejackson-databind emMaven Central.

3. Fonte JSON

Considere o seguinte JSON como o material de origem para nossos exemplos. Enquanto a estrutura é artificial, observe que incluímos propriedades aninhadas em dois níveis:

{
    "id": "957c43f2-fa2e-42f9-bf75-6e3d5bb6960a",
    "name": "The Best Product",
    "brand": {
        "id": "9bcd817d-0141-42e6-8f04-e5aaab0980b6",
        "name": "ACME Products",
        "owner": {
            "id": "b21a80b1-0c09-4be3-9ebd-ea3653511c13",
            "name": "Ultimate Corp, Inc."
        }
    }
}

4. Modelo de Domínio Simplificado

Em um modelo de domínio nivelado descrito pela classeProduct abaixo, extrairemosbrandName, que está aninhado em um nível profundo em nosso JSON de origem. Além disso, extrairemosownerName, que está aninhado em dois níveis de profundidade e dentro do objetobrand aninhado:

public class Product {

    private String id;
    private String name;
    private String brandName;
    private String ownerName;

    // standard getters and setters
}

5. Mapeamento com anotações

Para mapear a propriedadebrandName aninhada, primeiro precisamos descompactar o objetobrand aninhado emMape extrair a propriedadename. Então, para mapearownerName, descompactamos o objetoowner aninhado em umMape extraímos sua propriedadename.

Podemos instruir Jackson aunpack the nested property by using a combination of @JsonProperty and some custom logic que adicionaremos à nossa classeProduct:

public class Product {
    // ...

    @SuppressWarnings("unchecked")
    @JsonProperty("brand")
    private void unpackNested(Map brand) {
        this.brandName = (String)brand.get("name");
        Map owner = (Map)brand.get("owner");
        this.ownerName = owner.get("name");
    }
}

Nosso código de cliente agora pode usar umObjectMapper para transformar nosso JSON de origem, que existe como aString constanteSOURCE_JSON dentro da classe de teste:

@Test
public void whenUsingAnnotations_thenOk() throws IOException {
    Product product = new ObjectMapper()
      .readerFor(Product.class)
      .readValue(SOURCE_JSON);

    assertEquals(product.getName(), "The Best Product");
    assertEquals(product.getBrandName(), "ACME Products");
    assertEquals(product.getOwnerName(), "Ultimate Corp, Inc.");
}

6. Mapeando comJsonNode

Mapear uma estrutura de dados aninhada comJsonNode requer um pouco mais de trabalho. Aqui, usamosObjectMapper'sreadTree para analisar os campos desejados:

@Test
public void whenUsingJsonNode_thenOk() throws IOException {
    JsonNode productNode = new ObjectMapper().readTree(SOURCE_JSON);

    Product product = new Product();
    product.setId(productNode.get("id").textValue());
    product.setName(productNode.get("name").textValue());
    product.setBrandName(productNode.get("brand")
      .get("name").textValue());
    product.setOwnerName(productNode.get("brand")
      .get("owner").get("name").textValue());

    assertEquals(product.getName(), "The Best Product");
    assertEquals(product.getBrandName(), "ACME Products");
    assertEquals(product.getOwnerName(), "Ultimate Corp, Inc.");
}

7. Mapeamento comJsonDeserializer personalizado

O mapeamento de uma estrutura de dados aninhada com umJsonDeserializer customizado é idêntico à abordagemJsonNode do ponto de vista da implementação. Primeiro criamos oJsonDeserializer:

public class ProductDeserializer extends StdDeserializer {

    public ProductDeserializer() {
        this(null);
    }

    public ProductDeserializer(Class vc) {
        super(vc);
    }

    @Override
    public Product deserialize(JsonParser jp, DeserializationContext ctxt)
      throws IOException, JsonProcessingException {

        JsonNode productNode = jp.getCodec().readTree(jp);
        Product product = new Product();
        product.setId(productNode.get("id").textValue());
        product.setName(productNode.get("name").textValue());
        product.setBrandName(productNode.get("brand")
          .get("name").textValue());
        product.setOwnerName(productNode.get("brand").get("owner")
          .get("name").textValue());
        return product;
    }
}

7.1. Registro manual do desserializador

Para registrar manualmente nosso desserializador personalizado, nosso código de cliente deve adicionarJsonDeserializer aModule, registrarModule comObjectMapper e chamarreadValue:

@Test
public void whenUsingDeserializerManuallyRegistered_thenOk()
 throws IOException {

    ObjectMapper mapper = new ObjectMapper();
    SimpleModule module = new SimpleModule();
    module.addDeserializer(Product.class, new ProductDeserializer());
    mapper.registerModule(module);

    Product product = mapper.readValue(SOURCE_JSON, Product.class);

    assertEquals(product.getName(), "The Best Product");
    assertEquals(product.getBrandName(), "ACME Products");
    assertEquals(product.getOwnerName(), "Ultimate Corp, Inc.");
}

7.2. Registro automático do desserializador

Como alternativa ao registro manual deJsonDeserializer,, podemosregister the deserializer directly on the class:

@JsonDeserialize(using = ProductDeserializer.class)
public class Product {
    // ...
}

Com essa abordagem, não há necessidade de se registrar manualmente. Vamos dar uma olhada em nosso código de cliente usando o registro automático:

@Test
public void whenUsingDeserializerAutoRegistered_thenOk()
  throws IOException {

    ObjectMapper mapper = new ObjectMapper();
    Product product = mapper.readValue(SOURCE_JSON, Product.class);

    assertEquals(product.getName(), "The Best Product");
    assertEquals(product.getBrandName(), "ACME Products");
    assertEquals(product.getOwnerName(), "Ultimate Corp, Inc.");
}

8. Conclusão

Neste tutorial, demonstramos várias maneiras de usarJackson to parse JSON containing nested values. Dê uma olhada em nossa página principalJackson Tutorial para mais exemplos.

E, como sempre, trechos de código podem ser encontradosover on GitHub.