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

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

Открыта

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 лекция
Недоступна
Обработка списка городов для путеводителя 🗺️
Обработка списка городов для путеводителя 🗺️
Комментарии (2)
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны авторизоваться
Zlyden'
Уровень 1
13 декабря 2025, 15:02
Вот на сериализацию вы выделили 2-3 уровня... Что мешало сделать 1-2 уровня по одной библиотеке, потом 1-2 по второй ? Вы намешали всё, так максимально не усваивается / не запоминается...
Gans Electro
Уровень 59
3 октября, 21:17
Не согласен. Это базовый курс. Больше задач - ДА! Но 1 уровень на каждую библиотеку было бы расточительно. Что еще нужно здесь знать? Тем более GSON большинству вообще не будет нужен. В каком нибудь продвинутом курсе, например по Spring это было бы уместно. Да и лучше на другие темы делать доп материал, чем на этот. Можно переключится на старый курс и там еще пройти, да и никто же в гугле не банит за то что JR проходишь. Хотели бы курс на 200 уровней? Даже скажу что будь здесь хоть 10 уровней по Jackson, вы бы все забыли через пол года, если часто не работать с ними. Такие темы забываются, и их вспоминают только когда это нужно, при этом гугля. Сам проходил эту тему подробно 2 года назад, а теперь вообще не помню что тогда учил. Попросту время потратил. В обучений важно чтобы темы были эффективные, не долгие и не сложные