JavaRush /Курси /C# SELF /Динамічні структури: JObje...

Динамічні структури: 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"] = "UAH";        // Додали нову властивість
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, навіть без заздалегідь описаних моделей.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ