Работа с узлами модели дерева в Джексоне
1. обзор
В этом руководстве основное внимание уделяется работе сtree model nodes in Jackson.
Мы будем использоватьJsonNode для различных преобразований, а также для добавления, изменения и удаления узлов.
2. Создание узла
Первым шагом в создании узла является создание экземпляра объектаObjectMapper с помощью конструктора по умолчанию:
ObjectMapper mapper = new ObjectMapper();
Поскольку создание объектаObjectMapper является дорогостоящим, рекомендуется повторно использовать один и тот же объект для нескольких операций.
Затем у нас есть три разных способа создать узел дерева, когда у нас естьObjectMapper.
2.1. Создайте узел с нуля
Наиболее распространенный способ создать узел из ничего:
JsonNode node = mapper.createObjectNode();
В качестве альтернативы мы также можем создать узел черезJsonNodeFactory:
JsonNode node = JsonNodeFactory.instance.objectNode();
2.2. Разбор из источника JSON
Этот метод подробно описан в статьеJackson – Marshall String to JsonNode. Пожалуйста, обратитесь к нему, если вам нужно больше информации.
2.3. Преобразовать из объекта
Узел можно преобразовать из объекта Java, вызвав методvalueToTree(Object fromValue) дляObjectMapper:
JsonNode node = mapper.valueToTree(fromValue);
Здесь также может оказаться полезным APIconvertValue:
JsonNode node = mapper.convertValue(fromValue, JsonNode.class);
Давайте посмотрим, как это работает на практике. Предположим, у нас есть класс с именемNodeBean:
public class NodeBean {
private int id;
private String name;
public NodeBean() {
}
public NodeBean(int id, String name) {
this.id = id;
this.name = name;
}
// standard getters and setters
}
Напишем тест, чтобы убедиться, что преобразование происходит правильно:
@Test
public void givenAnObject_whenConvertingIntoNode_thenCorrect() {
NodeBean fromValue = new NodeBean(2016, "example.com");
JsonNode node = mapper.valueToTree(fromValue);
assertEquals(2016, node.get("id").intValue());
assertEquals("example.com", node.get("name").textValue());
}
3. Преобразование узла
3.1. Записать как JSON
Основной метод преобразования узла дерева в строку JSON заключается в следующем:
mapper.writeValue(destination, node);
где адресатом может бытьFile,OutputStream илиWriter. Повторно используя классNodeBean, объявленный в разделе 2.3, тест проверяет, работает ли этот метод должным образом:
final String pathToTestFile = "node_to_json_test.json";
@Test
public void givenANode_whenModifyingIt_thenCorrect() throws IOException {
String newString = "{\"nick\": \"cowtowncoder\"}";
JsonNode newNode = mapper.readTree(newString);
JsonNode rootNode = ExampleStructure.getExampleRoot();
((ObjectNode) rootNode).set("name", newNode);
assertFalse(rootNode.path("name").path("nick").isMissingNode());
assertEquals("cowtowncoder", rootNode.path("name").path("nick").textValue());
}
3.2. Преобразовать в объект
Самый удобный способ конвертироватьJsonNode в объект Java - этоtreeToValue API:
NodeBean toValue = mapper.treeToValue(node, NodeBean.class);
Что функционально эквивалентно:
NodeBean toValue = mapper.convertValue(node, NodeBean.class)
Мы также можем сделать это через поток токенов:
JsonParser parser = mapper.treeAsTokens(node);
NodeBean toValue = mapper.readValue(parser, NodeBean.class);
Наконец, давайте проведем тест, который проверяет процесс преобразования:
@Test
public void givenANode_whenConvertingIntoAnObject_thenCorrect()
throws JsonProcessingException {
JsonNode node = mapper.createObjectNode();
((ObjectNode) node).put("id", 2016);
((ObjectNode) node).put("name", "example.com");
NodeBean toValue = mapper.treeToValue(node, NodeBean.class);
assertEquals(2016, toValue.getId());
assertEquals("example.com", toValue.getName());
}
4. Управление узлами дерева
Следующие элементы JSON, содержащиеся в файле с именемexample.json, используются в качестве базовой структуры для действий, обсуждаемых в этом разделе:
{
"name":
{
"first": "Tatu",
"last": "Saloranta"
},
"title": "Jackson founder",
"company": "FasterXML"
}
Этот файл JSON, расположенный на пути к классам, анализируется в дереве модели:
public class ExampleStructure {
private static ObjectMapper mapper = new ObjectMapper();
static JsonNode getExampleRoot() throws IOException {
InputStream exampleInput =
ExampleStructure.class.getClassLoader()
.getResourceAsStream("example.json");
JsonNode rootNode = mapper.readTree(exampleInput);
return rootNode;
}
}
Обратите внимание, что корень дерева будет использоваться при иллюстрации операций над узлами в следующих подразделах.
4.1. Поиск узла
Прежде чем работать на каком-либо узле, первое, что нам нужно сделать, это найти его и присвоить переменной.
Если путь к узлу известен заранее, это сделать довольно просто. Например, предположим, что нам нужен узел с именемlast, который находится под узломname:
JsonNode locatedNode = rootNode.path("name").path("last");
В качестве альтернативы APIget илиwith также могут использоваться вместоpath.
Если путь неизвестен, поиск, конечно, станет более сложным и повторяющимся.
Мы можем увидеть пример перебора всех узлов в5. Iterating Over the Nodes
4.2. Добавление нового узла
Узел может быть добавлен как дочерний узел другого узла следующим образом:
ObjectNode newNode = ((ObjectNode) locatedNode).put(fieldName, value);
Многие перегруженные вариантыput могут использоваться для добавления новых узлов с разными типами значений.
Также доступны многие другие подобные методы, включаяputArray,putObject,PutPOJO,putRawValue иputNull.
Наконец, давайте посмотрим на пример, в котором мы добавляем целую структуру к корневому узлу дерева:
"address":
{
"city": "Seattle",
"state": "Washington",
"country": "United States"
}
Вот полный тест, в котором проходят все эти операции и проверяются результаты:
@Test
public void givenANode_whenAddingIntoATree_thenCorrect() throws IOException {
JsonNode rootNode = ExampleStructure.getExampleRoot();
ObjectNode addedNode = ((ObjectNode) rootNode).putObject("address");
addedNode
.put("city", "Seattle")
.put("state", "Washington")
.put("country", "United States");
assertFalse(rootNode.path("address").isMissingNode());
assertEquals("Seattle", rootNode.path("address").path("city").textValue());
assertEquals("Washington", rootNode.path("address").path("state").textValue());
assertEquals(
"United States", rootNode.path("address").path("country").textValue();
}
4.3. Редактирование узла
ЭкземплярObjectNode можно изменить, вызвав методset(String fieldName, JsonNode value):
JsonNode locatedNode = locatedNode.set(fieldName, value);
Аналогичных результатов можно добиться, используя методыreplace илиsetAll для объектов одного типа.
Чтобы убедиться, что метод работает должным образом, мы изменим значение поляname под корневым узлом с объектаfirst иlast на другое, состоящее только изnickполе s в тесте:
@Test
public void givenANode_whenModifyingIt_thenCorrect() throws IOException {
String newString = "{\"nick\": \"cowtowncoder\"}";
JsonNode newNode = mapper.readTree(newString);
JsonNode rootNode = ExampleStructure.getExampleRoot();
((ObjectNode) rootNode).set("name", newNode);
assertFalse(rootNode.path("name").path("nick").isMissingNode());
assertEquals("cowtowncoder", rootNode.path("name").path("nick").textValue());
}
4.4. Удаление узла
Узел можно удалить, вызвав APIremove(String fieldName) на его родительском узле:
JsonNode removedNode = locatedNode.remove(fieldName);
Чтобы удалить сразу несколько узлов, мы можем вызвать перегруженный метод с параметром типаCollection<String>, который возвращает родительский узел вместо удаляемого:
ObjectNode locatedNode = locatedNode.remove(fieldNames);
В крайнем случае, когда мы хотим удалить все подузлы данного узла–, нам пригодится APIremoveAll.
Следующий тест сфокусируется на первом упомянутом выше методе, который является наиболее распространенным сценарием:
@Test
public void givenANode_whenRemovingFromATree_thenCorrect() throws IOException {
JsonNode rootNode = ExampleStructure.getExampleRoot();
((ObjectNode) rootNode).remove("company");
assertTrue(rootNode.path("company").isMissingNode());
}
5. Итерация по узлам
Давайте переберем все узлы в документе JSON и переформатируем их в YAML. JSON имеет три типа узла: Value, Object и Array.
Итак, давайте удостоверимся, что наша выборка данных имеет все три разных типа, добавивArray:
{
"name":
{
"first": "Tatu",
"last": "Saloranta"
},
"title": "Jackson founder",
"company": "FasterXML",
"pets" : [
{
"type": "dog",
"number": 1
},
{
"type": "fish",
"number": 50
}
]
}
Теперь давайте посмотрим, какой YAML мы хотим создать:
name:
first: Tatu
last: Saloranta
title: Jackson founder
company: FasterXML
pets:
- type: dog
number: 1
- type: fish
number: 50
Мы знаем, что узлы JSON имеют иерархическую древовидную структуру. Итак, самый простой способ перебрать весь документ JSON - это начать сверху и пройти вниз по всем дочерним узлам.
Мы передадим корневой узел в рекурсивный метод. Затем метод будет вызывать себя для каждого дочернего элемента предоставленного узла.
5.1. Тестирование итерации
Мы начнем с создания простого теста, который проверяет, можем ли мы успешно преобразовать JSON в YAML.
Наш тест предоставляет корневой узел документа JSON нашему методуtoYaml и утверждает, что возвращаемое значение соответствует нашим ожиданиям:
@Test
public void givenANodeTree_whenIteratingSubNodes_thenWeFindExpected() throws IOException {
JsonNode rootNode = ExampleStructure.getExampleRoot();
String yaml = onTest.toYaml(rootNode);
assertEquals(expectedYaml, yaml);
}
public String toYaml(JsonNode root) {
StringBuilder yaml = new StringBuilder();
processNode(root, yaml, 0);
return yaml.toString(); }
}
5.2. Работа с разными типами узлов
Нам нужно обрабатывать разные типы узлов немного по-разному. Сделаем это в нашем методеprocessNode:
private void processNode(JsonNode jsonNode, StringBuilder yaml, int depth) {
if (jsonNode.isValueNode()) {
yaml.append(jsonNode.asText());
}
else if (jsonNode.isArray()) {
for (JsonNode arrayItem : jsonNode) {
appendNodeToYaml(arrayItem, yaml, depth, true);
}
}
else if (jsonNode.isObject()) {
appendNodeToYaml(jsonNode, yaml, depth, false);
}
}
Во-первых, давайте рассмотрим узел Value. Мы просто вызываем методasText узла, чтобы получить представление значенияString.
Затем давайте посмотрим на узел массива. Каждый элемент в узле массива сам по себе являетсяJsonNode, поэтому мы перебираем массив и передаем каждый узел методуappendNodeToYaml. Нам также нужно знать, что эти узлы являются частью массива.
К сожалению, сам узел не содержит ничего, что говорило бы нам об этом, поэтому мы передадим флаг в наш методappendNodeToYaml.
Наконец, мы хотим перебрать все дочерние узлы каждого узла Object. Один из вариантов - использоватьJsonNode.elements. Однако мы не можем определить имя поля по элементу, поскольку оно просто содержит значение поля:
Object {"first": "Tatu", "last": "Saloranta"}
Value "Jackson Founder"
Value "FasterXML"
Array [{"type": "dog", "number": 1},{"type": "fish", "number": 50}]
Вместо этого мы будем использоватьJsonNode.fields, поскольку это дает нам доступ как к имени поля, так и к значению:
Key="name", Value=Object {"first": "Tatu", "last": "Saloranta"}
Key="title", Value=Value "Jackson Founder"
Key="company", Value=Value "FasterXML"
Key="pets", Value=Array [{"type": "dog", "number": 1},{"type": "fish", "number": 50}]
Для каждого поля мы добавляем имя поля в вывод. Затем обработайте значение как дочерний узел, передав его методуprocessNode:
private void appendNodeToYaml(JsonNode node, StringBuilder yaml, int depth, boolean isArrayItem) {
Iterator> fields = node.fields();
boolean isFirst = true;
while (fields.hasNext()) {
Entry jsonField = fields.next();
addFieldNameToYaml(yaml, jsonField.getKey(), depth, isArrayItem && isFirst);
processNode(jsonField.getValue(), yaml, depth+1);
isFirst = false;
}
}
По узлу мы не можем сказать, сколько у него предков. Итак, мы передаем поле с именем depth в методprocessNode, чтобы отслеживать это. Мы увеличиваем это значение каждый раз, когда получаем дочерний узел, чтобы мы могли правильно сделать отступ в полях нашего вывода YAML:
private void addFieldNameToYaml(
StringBuilder yaml, String fieldName, int depth, boolean isFirstInArray) {
if (yaml.length()>0) {
yaml.append("\n");
int requiredDepth = (isFirstInArray) ? depth-1 : depth;
for(int i = 0; i < requiredDepth; i++) {
yaml.append(" ");
}
if (isFirstInArray) {
yaml.append("- ");
}
}
yaml.append(fieldName);
yaml.append(": ");
}
Теперь, когда у нас есть весь код для перебора узлов и создания выходных данных YAML, мы можем запустить наш тест, чтобы показать, что он работает.
6. Заключение
В этом руководстве рассматриваются общие API-интерфейсы и сценарии работы с древовидной моделью в Джексоне.
И, как всегда, реализацию всех этих примеров и фрагментов кода можно найти вover on GitHub - это проект на основе Maven, поэтому его должно быть легко импортировать и запускать как есть.