JavaRush /Курсы /C# SELF /Динамические структуры: JO...

Динамические структуры: JObject, JArray

C# SELF
47 уровень , 3 лекция
Открыта

1. Введение

В предыдущих лекциях мы всегда работали со строго типизированными структурами. Например, у нас есть класс Person, который мы сериализуем в JSON и обратно:

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

Но иногда вы не знаете заранее структуру данных. Например:

  • Вы пишете парсер для внешнего сервиса, где структура ответа не фиксирована.
  • Вам нужно извлечь только часть информации, не обязательно заполнять целый класс.
  • Нужно отредактировать или сгенерировать JSON "на лету", основываясь на динамических условиях.

Тут и вступают в игру "динамические структуры" — объекты, которые хранят JSON-дерево как набор ключей и значений, не требуя заранее описанного C#-класса.

Почему чаще всего используют Newtonsoft.Json

В .NET есть два главных игрока на поле работы с JSON:

  • System.Text.Json — встроенная библиотека Microsoft (развивается с .NET Core 3.0).
  • Newtonsoft.Json (Json.NET) — популярная библиотека, давшая миру C# такие классы, как JObject и JArray.

На момент написания лекции System.Text.Json всё ещё не предлагает полного аналога JObject/JArray с тем же удобством. Поэтому, если нужно часто разбирать или модифицировать сложные/неизвестные JSON-структуры, чаще выбирают Newtonsoft.Json.

Основные типы: JObject, JArray, JValue и семейство JToken

  • JToken — базовый тип для всех JSON-узлов.
  • JObject — JSON-объект { ... }, набор пар "ключ-значение".
  • JArray — массив [ ... ].
  • JValue — отдельное значение (42, "текст", true, null).

Идея проста: парсим JSON — получаем "дерево" из этих токенов и можем по нему ходить, искать, менять, удалять и добавлять элементы.

2. Чтение неизвестного JSON

Допустим, нам пришёл вот такой JSON, и мы не хотим или не можем заранее писать под него класс:

{
  "status": "ok",
  "amount": 150.5,
  "items": [
    {
      "name": "book",
      "qty": 1
    },
    {
      "name": "pen",
      "qty": 3
    }
  ]
}

С помощью Newtonsoft.Json мы можем превратить его в дерево и исследовать на лету.

Пример: чтение JSON в JObject

using Newtonsoft.Json.Linq;

string json = @"{
  ""status"": ""ok"",
  ""amount"": 150.5,
  ""items"": [
    { ""name"": ""book"", ""qty"": 1 },
    { ""name"": ""pen"", ""qty"": 3 }
  ]
}";

// Парсим строку и получаем дерево
JObject root = JObject.Parse(json);

// Берём свойства как из словаря
string status = (string)root["status"]; // "ok"
double amount = (double)root["amount"]; // 150.5

// items — это массив, а значит JArray
JArray items = (JArray)root["items"];

// Перебираем массив
foreach (JObject item in items)
{
    string name = (string)item["name"];
    int qty = (int)item["qty"];
    Console.WriteLine($"Товар: {name}, Кол-во: {qty}");
}

Потрясающе удобно: никакого объявления классов — можно быстро выковырять нужные куски.

3. Индексаторы и динамический доступ

Индексаторы:

  • Для объекта: root["status"], root["items"]
  • Для массива: items[0], items[1]

Чтобы получить значение сразу в нужном типе, используйте приведение (string), (int), (bool), (double) — библиотека автоматически преобразует тип.

Если данных может не быть, осторожнее: доступ к отсутствующему ключу вернёт null, и явное приведение упадёт. Лучше использовать методы с проверкой:

if (root.TryGetValue("amount", out var token))
{
    double amount = token.Value<double>();
    // Такой способ удобнее: Value<T>() – сразу преобразует
}

Вложенные объекты и массивы тоже читаются просто:

// Получить второй товар
JObject secondItem = (JObject)root["items"][1];
string itemName = (string)secondItem["name"]; // "pen"

Можно и динамически:

dynamic droot = root;
Console.WriteLine(droot.status); // "ok"

Но помните: с dynamic компилятор не проверит доступ к полям — ошибки всплывут только во время выполнения.

4. Модификация JSON-дерева на лету

Добавление элементов

root["currency"] = "RUB";        // Добавили новое свойство
items.Add(new JObject
{
    ["name"] = "eraser",
    ["qty"] = 2
});

Изменение и удаление

root["status"] = "done";         // Изменили значение
items[0]["qty"] = 5;             // Увеличили количество первого товара
items.RemoveAt(1);               // Удалили второй товар

Итоговое сохранение в строку

string modifiedJson = root.ToString();
// Или root.ToString(Formatting.Indented) для красоты

5. Создание JSON-структуры с нуля

var person = new JObject
{
    ["name"] = "Alice",
    ["age"] = 22,
    ["languages"] = new JArray { "C#", "Python" },
    ["isStudent"] = true
};

Console.WriteLine(person.ToString(Newtonsoft.Json.Formatting.Indented));
{
  "name": "Alice",
  "age": 22,
  "languages": [
    "C#",
    "Python"
  ],
  "isStudent": true
}

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

6. Как собрать свой объект из JObject

Иногда нужно превратить гибкий JSON в строгий C#-объект. Варианты:

  • Обычная десериализация: JsonConvert.DeserializeObject<MyClass>(...).
  • Ручная сборка объекта, извлекая нужные поля из JObject.
class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

// Допустим, нам прилетел вот такой JSON:
string incoming = @"{ ""name"":""Bob"", ""age"":30, ""extraField"":true }";

JObject j = JObject.Parse(incoming);

// Собираем объект вручную — только нужные поля
var person = new Person
{
    Name = (string)j["name"],
    Age = (int)j["age"],
    // extraField нам не нужен — и отлично!
};

7. Примеры ошибок и нюансов: типичные ловушки

Работа с динамическими JSON-структурами гибка, но есть "подводные камни":

  • Обращение к несуществующему ключу/элементу даёт null; явное приведение к значимому типу вызовет исключение.
  • Несоответствие ожидаемого типа (ожидаем объект, а пришло значение) — ошибка приведения.
  • Для перебора полей объекта используйте Properties():
foreach (var prop in root.Properties())
{
    Console.WriteLine($"Поле: {prop.Name}, Значение: {prop.Value}");
}
  • У Newtonsoft.Json удобно работать с LINQ-подобными запросами (фильтрация, поиск):
var expensiveItems = items.Where(obj => (int)obj["qty"] > 2);
foreach (var item in expensiveItems)
    Console.WriteLine(item);

8. Типичные ошибки при работе с JObject/JArray

Ошибка №1: отсутствие ожидаемого поля или несовпадение типа. Очень часто разработчик рассчитывает, что в объекте будет определённое поле, но его либо нет, либо оно другого типа. Если ожидается объект, а приходит число, при приведении типов возникнет исключение. Проверяйте наличие и тип перед использованием.

Ошибка №2: обращение к полям вложенных структур без проверки на null. Когда в JSON есть вложенные объекты, а ключи отличаются или отсутствуют, попытка обратиться к отсутствующему полю может привести к падению. Делайте проверку на null перед чтением значений вложенных узлов.

Ошибка №3: приведение к значимому типу при значении null. Если ключ существует, но его значение равно null, выражение вроде (int)j["age"] вызовет исключение. Используйте Value<T>() — он вернёт значение по умолчанию (для int это 0, для строк — null).

Ошибка №4: чрезмерное увлечение динамическими объектами вместо чётких моделей. Слишком сложные структуры удобнее и безопаснее описать C#-классами: это уменьшит количество ошибок и повысит читаемость кода. Динамику применяйте там, где структура действительно неизвестна или сильно варьируется.

Теперь вы знаете, как быстро и безопасно парсить, изменять и создавать JSON-структуры с помощью JObject и JArray, даже без заранее описанных моделей.

2
Задача
C# SELF, 47 уровень, 3 лекция
Недоступна
Генерация JSON "на лету"
Генерация JSON "на лету"
Комментарии (1)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Slevin Уровень 59
27 февраля 2026
Вот это хорошая лекция. Потому что новая информация. 👍