«1. Введение

Работать с предопределенными структурами данных JSON с помощью Jackson очень просто. Однако иногда нам нужно обрабатывать динамические объекты JSON с неизвестными свойствами.

В этом кратком руководстве мы рассмотрим несколько способов сопоставления динамических объектов JSON с классами Java.

Обратите внимание, что во всех тестах мы предполагаем, что у нас есть поле objectMapper типа com.fasterxml.jackson.databind.ObjectMapper.

2. Использование JsonNode

Допустим, мы хотим обработать спецификации товаров в интернет-магазине. Все продукты имеют некоторые общие свойства, но есть и другие, которые зависят от типа продукта.

Например, мы хотим знать соотношение сторон дисплея мобильного телефона, но это свойство не имеет особого смысла для обуви.

Структура данных выглядит так:

{
    "name": "Pear yPhone 72",
    "category": "cellphone",
    "details": {
        "displayAspectRatio": "97:3",
        "audioConnector": "none"
    }
}

Мы храним динамические свойства в объекте details.

Мы можем сопоставить общие свойства со следующим классом Java:

class Product {

    String name;
    String category;

    // standard getters and setters
}

Кроме того, нам нужно соответствующее представление для объекта details. Например, com.fasterxml.jackson.databind.JsonNode может обрабатывать динамические ключи.

Чтобы использовать его, мы должны добавить его как поле в наш класс Product:

class Product {

    // common fields

    JsonNode details;

    // standard getters and setters
}

Наконец, мы проверяем, что он работает:

String json = "<json object>";

Product product = objectMapper.readValue(json, Product.class);

assertThat(product.getName()).isEqualTo("Pear yPhone 72");
assertThat(product.getDetails().get("audioConnector").asText()).isEqualTo("none");

Однако у нас есть проблема с этим решением. Наш класс зависит от библиотеки Джексона, так как у нас есть поле JsonNode.

3. Использование карты

Мы можем решить эту проблему, используя java.util.Map для поля сведений. Точнее, мы должны использовать Map\u003cString, Object\u003e.

Все остальное может остаться прежним:

class Product {

    // common fields

    Map<String, Object> details;

    // standard getters and setters
}

И тогда мы можем проверить это с помощью теста:

String json = "<json object>";

Product product = objectMapper.readValue(json, Product.class);

assertThat(product.getName()).isEqualTo("Pear yPhone 72");
assertThat(product.getDetails().get("audioConnector")).isEqualTo("none");

4. Использование @JsonAnySetter

Предыдущие решения хороши, когда объект содержит только динамические свойства. Однако иногда в одном объекте JSON смешаны фиксированные и динамические свойства.

Например, нам может понадобиться сгладить представление нашего продукта:

{
    "name": "Pear yPhone 72",
    "category": "cellphone",
    "displayAspectRatio": "97:3",
    "audioConnector": "none"
}

Мы можем рассматривать подобную структуру как динамический объект. К сожалению, это означает, что мы не можем определить общие свойства — мы также должны обрабатывать их динамически.

В качестве альтернативы мы могли бы использовать @JsonAnySetter, чтобы пометить метод для обработки дополнительных неизвестных свойств. Такой метод должен принимать два аргумента: имя и значение свойства:

class Product {

    // common fields

    Map<String, Object> details = new LinkedHashMap<>();

    @JsonAnySetter
    void setDetail(String key, Object value) {
        details.put(key, value);
    }

    // standard getters and setters
}

Обратите внимание, что мы должны создать экземпляр объекта details, чтобы избежать исключений NullPointerException.

Поскольку мы храним динамические свойства в карте, мы можем использовать ее так же, как и раньше:

String json = "<json object>";

Product product = objectMapper.readValue(json, Product.class);

assertThat(product.getName()).isEqualTo("Pear yPhone 72");
assertThat(product.getDetails().get("audioConnector")).isEqualTo("none");

5. Создание пользовательского десериализатора

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

Мы можем настроить эти ситуации с помощью специального десериализатора. Поскольку это сложная тема, мы рассмотрим ее в другой статье «Начало работы с пользовательской десериализацией в Джексоне».

6. Заключение

В этой статье мы рассмотрели несколько способов обработки динамических объектов JSON с помощью Jackson.

Как обычно, примеры доступны на GitHub.