Рубрики
Без рубрики

Работа с узлами модели дерева в Джексоне

Узнайте, как создавать, находить и манипулировать объектами JsonNode в Jackson 2.

Автор оригинала: baeldung.

Работа с узлами модели дерева в Джексоне

1. Обзор

Этот учебник будет сосредоточен на работе с узлы модели дерева в Джексоне .

Мы будем использовать JsonNode для различных конверсий, а также добавления, изменения и удаления узлов.

2. Создание узла

Первым шагом в создании узла является мгновенная ОбъектМаппер объект с помощью конструктора по умолчанию:

ObjectMapper mapper = new ObjectMapper();

С момента создания ОбъектМаппер объект стоит дорого, рекомендуется повторно использовать один и тот же объект для нескольких операций.

Далее, у нас есть три различных способа создать узел дерева, как только у нас есть ОбъектМаппер .

2.1. Построить узел из царапин

Наиболее распространенным способом создания узла из ничего является следующее:

JsonNode node = mapper.createObjectNode();

Кроме того, мы также можем создать узел через JsonNodeFactory :

JsonNode node = JsonNodeFactory.instance.objectNode();

2.2. Парс из источника JSON

Этот метод хорошо охвачены в Джексон – Маршалл Стринг JsonNode статьи. Пожалуйста, обратитесь к нему, если вам нужно больше информации.

2.3. Преобразование из объекта

Узел может быть преобразован из объекта Java, позвонив в valueToTree (объект отValue) метод на ОбъектМаппер :

JsonNode node = mapper.valueToTree(fromValue);

конвертироватьВалю API также полезен здесь:

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, "baeldung.com");

    JsonNode node = mapper.valueToTree(fromValue);

    assertEquals(2016, node.get("id").intValue());
    assertEquals("baeldung.com", node.get("name").textValue());
}

3. Преобразование узла

3.1. Выпишите как JSON

Основным методом преобразования узла дерева в строку JSON является следующее:

mapper.writeValue(destination, node);

где пункт назначения может быть Файл , ВыходСтрим или Писатель .
Повторное 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-объект является деревоТоВалю 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", "baeldung.com");

    NodeBean toValue = mapper.treeToValue(node, NodeBean.class);

    assertEquals(2016, toValue.getId());
    assertEquals("baeldung.com", toValue.getName());
}

4. Манипулирование узлами деревьев

Следующие элементы JSON, содержащиеся в файле под названием example.json , используются в качестве базовой структуры для действий, обсуждаемых в этом разделе, которые должны быть приняты на:

{
    "name": 
        {
            "first": "Tatu",
            "last": "Saloranta"
        },

    "title": "Jackson founder",
    "company": "FasterXML"
}

Этот файл JSON, расположенный на classpath, разобраен на дерево модели:

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. Поиск узла

Прежде чем работать над каким-либо узел, первое, что нам нужно сделать, это найти и назначить его переменной.

Если путь к узелу известен заранее, это довольно легко сделать. Например, скажем, нам нужен узел под названием последний , который находится под имя узел:

JsonNode locatedNode = rootNode.path("name").path("last");

Кроме того, получить или с API также можно использовать вместо путь .

Если путь не известен, поиск, конечно, станет более сложным и итеративным.

Мы можем увидеть пример итерации по всем узлам в 5. Итерирование над узлами

4.2. Добавление нового узла

Узел может быть добавлен в качестве ребенка другого узла следующим образом:

ObjectNode newNode = ((ObjectNode) locatedNode).put(fieldName, value);

Многие перегруженные варианты положить могут использоваться для добавления новых узлов различных типов значений.

Многие другие аналогичные методы также доступны, в том числе положить Аррай , 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. Редактирование узла

ОбъектНоде экземпляр может быть изменен путем вызова набор (String fieldName, значение JsonNode) метод:

JsonNode locatedNode = locatedNode.set(fieldName, value);

Аналогичные результаты могут быть достигнуты с помощью заменить или setAll методы на объектах одного типа.

Чтобы убедиться, что метод работает, как ожидалось, мы изменим значение поля имя под корневой узел от объекта первый и последний в другой, состоящий только из Ник поле в тесте:

@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. Удаление узла

Узел можно удалить, позвонив в удалить (String fieldName) API на родительском узеле:

JsonNode removedNode = locatedNode.remove(fieldName);

Для того, чтобы удалить несколько узлов одновременно, мы можем вызвать перегруженный метод с параметром Тип , который возвращает родительский узел вместо того, который должен быть удален:

ObjectNode locatedNode = locatedNode.remove(fieldNames);

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

Следующий тест будет сосредоточен на первом методе, упомянутом выше, который является наиболее распространенным сценарием:

@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 имеет три типа узла, которые являются значение, объект, и 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 в наш вЯмл метод и утверждает, что возвращенное значение является то, что мы ожидаем:

@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. Обработка различных типов узла

Мы должны обрабатывать различные типы узла немного по-разному. Мы сделаем это в нашем процессНод метод:

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);
    }
}

Во-первых, рассмотрим узел значения. Мы просто называем asText метод узла, чтобы получить Струнные представление значения.

Далее рассмотрим узел Array. Каждый элемент в узлах Array сам по себе является JsonNode , поэтому мы итерировать над массивом и передать каждый узел приложениеНодеТоЯмл метод. Мы также должны знать, что эти узлы являются частью массива.

К сожалению, сам узел не содержит ничего, что говорит нам об этом, поэтому мы пересмотрим флаг в наш приложениеНодеТоЯмл метод.

Наконец, мы хотим итерировать все детские узлы каждого узла объекта. Одним из вариантов является использование 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}]

Для каждого поля мы добавляем название поля к выходу. Затем обработать значение как узел ребенка, передав его в процессНод метод:

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;
    }
        
}

Мы не можем сказать по узлам, сколько у него предков. Таким образом, мы проходим поле, называемое глубиной в процессНод метод, чтобы отслеживать это. Мы увеличиваем это значение каждый раз, когда получаем узел ребенка, чтобы правильно определить поля в нашем выходе 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 и сценарии работы с моделью дерева в Джексоне.

И, как всегда, реализация всех этих примеров и фрагментов кода можно найти в более на GitHub – это Maven основе проекта, поэтому она должна быть легко импортировать и работать, как она есть.