Перейти к основному содержимому

Сопоставление вложенных значений с Джексоном

· 4 мин. чтения

1. Обзор

Типичным вариантом использования при работе с JSON является выполнение преобразования из одной модели в другую. Например, нам может понадобиться разобрать сложный, плотно вложенный граф объектов в более простую модель для использования в другой области.

В этой быстрой статье мы рассмотрим, как отображать вложенные значения с помощью Jackson , чтобы сгладить сложную структуру данных. Мы будем десериализовать JSON тремя разными способами:

  • Использование @JsonProperty
  • Использование JsonNode
  • Использование пользовательского JsonDeserializer

2. Зависимость от Maven

Давайте сначала добавим в pom.xml следующую зависимость :

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.11.1</version>
</dependency>

Мы можем найти последние версии jackson-databind на Maven Central .

3. Источник JSON

Рассмотрим следующий JSON в качестве исходного материала для наших примеров. Хотя структура надуманная, обратите внимание, что мы включили свойства, вложенные на два уровня глубже:

{
"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. Упрощенная модель предметной области

В плоской модели предметной области, описанной ниже классом Product , мы извлечем brandName , который вложен на один уровень глубже в наш исходный JSON. Кроме того, мы извлечем ownerName , вложенный на два уровня глубже во вложенный объект бренда :

public class Product {

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

// standard getters and setters
}

5. Отображение с аннотациями

Чтобы сопоставить вложенное свойство brandName , нам сначала нужно распаковать вложенный объект brandName в Map и извлечь свойство name . Затем, чтобы сопоставить ownerName , мы распаковываем вложенный объект владельца в Map и извлекаем его свойство name .

Мы можем поручить Джексону распаковать вложенное свойство, используя комбинацию @JsonProperty и некоторой пользовательской логики , которую мы добавляем в наш класс Product :

public class Product {
// ...

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

Наш клиентский код теперь может использовать ObjectMapper для преобразования нашего исходного JSON, который существует как строковая константа SOURCE_JSON в тестовом классе: ``

@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. Сопоставление с JsonNode

Сопоставление вложенной структуры данных с помощью JsonNode требует немного больше работы. Здесь мы используем readTree ObjectMapper для анализа нужных полей:

@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. Сопоставление с пользовательским JsonDeserializer

Сопоставление вложенной структуры данных с пользовательским JsonDeserializer идентично подходу JsonNode с точки зрения реализации. Сначала мы создаем JsonDeserializer:

public class ProductDeserializer extends StdDeserializer<Product> {

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. Ручная регистрация десериализатора

Чтобы вручную зарегистрировать наш пользовательский десериализатор, наш клиентский код должен добавить JsonDeserializer в модуль , зарегистрировать модуль с помощью ObjectMapper и вызвать readValue:

@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. Автоматическая регистрация десериализатора

В качестве альтернативы ручной регистрации JsonDeserializer мы можем зарегистрировать десериализатор непосредственно в классе :

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

При таком подходе нет необходимости регистрироваться вручную. Давайте посмотрим на наш клиентский код, использующий автоматическую регистрацию:

@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. Заключение

В этом руководстве мы продемонстрировали несколько способов использования Джексона для разбора JSON, содержащего вложенные значения . Взгляните на нашу главную страницу учебника по Джексону , чтобы увидеть больше примеров.

И, как всегда, фрагменты кода можно найти на GitHub .