«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.