JavaRush /Курсы /JAVA 25 SELF /Работа с динамическими структурами: Map, List, JsonNode

Работа с динамическими структурами: Map, List, JsonNode

JAVA 25 SELF
46 уровень , 3 лекция
Открыта

1. Чтение JSON в Map и List

Обычно, когда мы работаем с JSON, мы заранее знаем его структуру и можем описать её в виде Java-класса. Но на практике всё бывает не так предсказуемо: поля могут появляться и пропадать, вложенность меняться. В таких случаях гораздо удобнее оперировать универсальными структурами данных — Map, List — или деревьями JSON.

Создавать Java-модель под каждую вариацию — долго и хрупко. Универсальные коллекции и деревья позволяют гибко извлекать только нужные части, не привязываясь к фиксированной схеме.

Десериализация JSON-объекта в Map

Пример JSON:

{
  "id": 123,
  "name": "Alice",
  "email": "alice@example.com",
  "active": true
}

Десериализация в Map:

import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Map;

public class Main {
    public static void main(String[] args) throws Exception {
        String json = "{\"id\":123,\"name\":\"Alice\",\"email\":\"alice@example.com\",\"active\":true}";

        ObjectMapper mapper = new ObjectMapper();
        Map<String, Object> data = mapper.readValue(json, Map.class);

        System.out.println(data);
        // Вывод: {id=123, name=Alice, email=alice@example.com, active=true}
    }
}

Теперь вы можете обращаться к любому полю как к элементу словаря:

System.out.println(data.get("name")); // Alice

Десериализация массива объектов в List

JSON-массив:

[
  { "id": 1, "name": "Alice" },
  { "id": 2, "name": "Bob" }
]

Десериализация:

import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.List;
import java.util.Map;

public class UsersReadExample {
    public static void main(String[] args) throws Exception {
        String json = "[{\"id\":1,\"name\":\"Alice\"},{\"id\":2,\"name\":\"Bob\"}]";

        ObjectMapper mapper = new ObjectMapper();
        List<Map<String, Object>> users = mapper.readValue(json, List.class);

        for (Map<String, Object> user : users) {
            System.out.println(user.get("name"));
        }
        // Вывод:
        // Alice
        // Bob
    }
}

Важный нюанс: при чтении в универсальные структуры все вложенные объекты превращаются в Map, а массивы — в List. Для сложных структур придётся аккуратно приводить типы и проверять их:

Object items = data.get("items");
if (items instanceof List) {
    List<?> itemList = (List<?>) items;
    // ...
}

2. JsonNode: дерево JSON в Jackson

Работать с Map/List удобно, но небезопасно: можно ошибиться с типом или пропустить вложенность. Jackson предлагает более мощный инструмент — класс JsonNode. Это универсальное дерево, где каждый узел — объект, массив, значение или null.

Получение JsonNode

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.JsonNode;

public class JsonNodeStart {
    public static void main(String[] args) throws Exception {
        String json = "{\"id\":123,\"name\":\"Alice\",\"tags\":[\"java\",\"json\"]}";
        ObjectMapper mapper = new ObjectMapper();

        JsonNode root = mapper.readTree(json);
        // root — это корень JSON-дерева
    }
}

Доступ к полям

int id = root.get("id").asInt();         // 123
String name = root.get("name").asText();  // Alice
JsonNode tags = root.get("tags");         // массив

System.out.println("Имя: " + name);

Обход массива

for (JsonNode tag : tags) {
    System.out.println(tag.asText());
}
// Вывод:
// java
// json

Вложенные объекты

Пример сложного JSON:

{
  "user": {
    "id": 1,
    "profile": {
      "nickname": "java_guru",
      "age": 25
    }
  }
}

Извлечение вложенного значения:

JsonNode profile = root.get("user").get("profile");
String nickname = profile.get("nickname").asText();
System.out.println(nickname); // java_guru

Безопасный доступ: get vs path

- get("ключ") — если ключа нет, вернёт null. Любой вызов вроде asText() на null приведёт к NullPointerException.
- path("ключ") — если ключа нет, вернёт «пустой» узел, и asText() даст "", asInt()0.

String phone = root.path("phone").asText(); // "" если поля нет

Проверка типа узла

if (root.has("tags") && root.get("tags").isArray()) {
    for (JsonNode tag : root.get("tags")) {
        // ...
    }
}

Модификация JsonNode

JsonNode — неизменяемый. Для создания/изменения дерева используйте ObjectNode/ArrayNode.

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.ArrayNode;

ObjectMapper mapper = new ObjectMapper();

// Создание нового объекта
ObjectNode obj = mapper.createObjectNode();
obj.put("id", 10);
obj.put("name", "Bob");

// Добавление массива
ArrayNode arr = mapper.createArrayNode();
arr.add("Java").add("JSON");
obj.set("tags", arr);

System.out.println(obj.toPrettyString());
/*
{
  "id" : 10,
  "name" : "Bob",
  "tags" : [ "Java", "JSON" ]
}
*/

3. Работа с Gson: JsonElement, JsonObject, JsonArray

Jackson — не единственный игрок. Gson тоже предоставляет удобное API для динамического JSON: JsonElement, JsonObject, JsonArray.

Парсинг в JsonElement

import com.google.gson.JsonElement;
import com.google.gson.JsonParser;

String json = "{\"id\":123,\"name\":\"Alice\",\"tags\":[\"java\",\"json\"]}";
JsonElement root = JsonParser.parseString(json);

Доступ к полям

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;

JsonObject obj = root.getAsJsonObject();
int id = obj.get("id").getAsInt();
String name = obj.get("name").getAsString();
JsonArray tags = obj.getAsJsonArray("tags");

for (JsonElement tag : tags) {
    System.out.println(tag.getAsString());
}

Вложенность и безопасность

if (obj.has("email")) {
    String email = obj.get("email").getAsString();
}

Работа с массивами

String arrJson = "[{\"id\":1},{\"id\":2}]";
JsonArray arr = JsonParser.parseString(arrJson).getAsJsonArray();

for (JsonElement el : arr) {
    JsonObject item = el.getAsJsonObject();
    System.out.println(item.get("id").getAsInt());
}

Модификация

В Gson объекты изменяемы — можно добавлять и удалять поля:

import com.google.gson.JsonObject;

JsonObject newObj = new JsonObject();
newObj.addProperty("id", 42);
newObj.add("tags", tags);
System.out.println(newObj.toString());

4. Практика: извлечение данных из неизвестного JSON

Задача: Пусть у нас есть JSON-конфиг, где структура может меняться:

{
  "service": "mail",
  "enabled": true,
  "params": {
    "host": "smtp.example.com",
    "port": 587
  }
}

Извлечь host и port с помощью Jackson:

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.JsonNode;

ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(json);
JsonNode params = root.path("params");
String host = params.path("host").asText();
int port = params.path("port").asInt();

System.out.println(host + ":" + port); // smtp.example.com:587

То же самое с Gson:

import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

JsonObject rootObj = JsonParser.parseString(json).getAsJsonObject();
JsonObject params = rootObj.getAsJsonObject("params");
String host = params.get("host").getAsString();
int port = params.get("port").getAsInt();

System.out.println(host + ":" + port);

5. Типичные ошибки и нюансы

Ошибка №1: Ошибочное приведение типов. Если вы ожидаете строку, а в поле число или null, вызов asText() или getAsString() даст неожиданный результат или исключение. Например, при отсутствии поля root.get("foo").asText() приведёт к NullPointerException.

Ошибка №2: Отсутствие проверки на null. Особенно при вложенных обращениях: root.get("params").get("host"). Если params отсутствует — будет NPE. В Jackson используйте path(), в Gson — проверяйте наличия через has и тип через isJsonObject()/isJsonArray().

Ошибка №3: Путаница с типами узлов. Если поле — массив, а вы обращаетесь к нему как к объекту — получите ошибку. Проверяйте типы: Jackson — isArray()/isObject(), Gson — isJsonArray()/isJsonObject().

Ошибка №4: Потеря информации о типах при работе с Map/List. При десериализации в Map<String, Object> вложенные структуры становятся каскадом Map/List, что усложняет навигацию и приводит к множеству приведений типов и проверок instanceof.

Ошибка №5: Непонимание (не)изменяемости дерева. В Jackson JsonNode неизменяем, а изменять нужно через ObjectNode и ArrayNode. В Gson объекты изменяемы; помните, что изменение узла затрагивает все ссылки на него в текущем дереве.

1
Задача
JAVA 25 SELF, 46 уровень, 3 лекция
Недоступна
Извлечение данных из игрового профиля 🎮
Извлечение данных из игрового профиля 🎮
1
Задача
JAVA 25 SELF, 46 уровень, 3 лекция
Недоступна
Обработка списка городов для путеводителя 🗺️
Обработка списка городов для путеводителя 🗺️
Комментарии (1)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Zlyden' Уровень 62
13 декабря 2025
Вот на сериализацию вы выделили 2-3 уровня... Что мешало сделать 1-2 уровня по одной библиотеке, потом 1-2 по второй ? Вы намешали всё, так максимально не усваивается / не запоминается...