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.
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.
Usando @JsonComponent no Spring Boot
Aprenda a usar a anotação @JsonComponent no Spring Boot.
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.