JSON в C# dynamic в 2025: десериализация без боли

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

JSON и C# dynamic: почему это всё ещё боль (и как её лечить)

Вы когда-нибудь смотрели на JSON-ответ от API и думали: «Господи, ну зачем ты такой сложный?» Особенно если структура данных меняется каждый раз, как погода в Питере. Классический пример — ответ от внешнего сервиса, где поля могут быть то строками, то массивами, то вообще null. Или когда вам нужно быстро распарсить JSON, но создавать под него отдельный класс — как пилить микросхему микроскопом.

Именно для таких случаев в C# есть магия dynamic. Вопрос «Deserialize JSON into C# dynamic object?» на Stack Overflow собрал тысячи просмотров — и это не случайно. В 2025 году, когда микросервисы и динамические API стали нормой, умение быстро и гибко работать с JSON — это суперсила разработчика.

В этой статье мы разберём лучшие практики десериализации JSON в dynamic object на C#. Без скучной теории, только код, примеры и реальные лайфхаки. Поехали!

Почему dynamic? А не просто JObject?

Хороший вопрос. В C# есть JObject (из Newtonsoft.Json) и JsonDocument (из System.Text.Json). Они тоже умеют парсить JSON «на лету». Но dynamic даёт одну важную вещь — синтаксический сахар.

Сравните:

// Через JObject
var json = JObject.Parse(rawJson);
var name = (string)json["user"]["name"];

// Через dynamic
dynamic obj = JsonConvert.DeserializeObject<dynamic>(rawJson);
var name = obj.user.name;

Второй вариант читается как обычный код, а не как «достань ключ, приведи тип, проверь на null». Это особенно удобно, когда JSON глубоко вложенный.

Способ 1: Классика — Newtonsoft.Json (Json.NET)

Да, 2025 год на дворе, но Newtonsoft.Json всё ещё жив и здоров. Многие legacy-проекты, да и некоторые современные, используют именно его. Почему? Гибкость, расширяемость и миллион настроек.

Базовый пример

using Newtonsoft.Json;

string json = @"
{
    'name': 'Алексей',
    'age': 30,
    'skills': ['C#', 'Python', 'SQL'],
    'address': {
        'city': 'Москва',
        'zip': 101000
    }
}";

dynamic obj = JsonConvert.DeserializeObject<dynamic>(json);

Console.WriteLine(obj.name);        // Алексей
Console.WriteLine(obj.age);         // 30
Console.WriteLine(obj.skills[0]);   // C#
Console.WriteLine(obj.address.city); // Москва

Всё просто и элегантно. Но есть нюанс: при работе с dynamic вы теряете IntelliSense и проверку типов на этапе компиляции. Ошибка в имени поля проявится только в рантайме. Поэтому:

  • Используйте dynamic только для чтения данных, которые вы не контролируете.
  • Обязательно оборачивайте вызовы в try-catch.
  • Не злоупотребляйте — если структура JSON стабильна, лучше создать DTO-класс.

Способ 2: Современный — System.Text.Json

Начиная с .NET Core 3.0, Microsoft продвигает System.Text.Json — быстрый, нативный, без внешних зависимостей. В 2025 году это стандарт для новых проектов.

Но есть подвох: System.Text.Json не умеет десериализовать в dynamic «из коробки». Придётся немного извернуться.

Вариант A: Через JsonDocument и ExpandoObject

using System.Text.Json;
using System.Dynamic;

string json = @"{""name"":""Елена"",""age"":25,""active"":true}";

using JsonDocument doc = JsonDocument.Parse(json);
dynamic obj = ConvertToExpando(doc.RootElement);

Console.WriteLine(obj.name);   // Елена
Console.WriteLine(obj.age);    // 25
Console.WriteLine(obj.active); // True

static ExpandoObject ConvertToExpando(JsonElement element)
{
    var expando = new ExpandoObject();
    var dict = (IDictionary<string, object>)expando;

    foreach (var property in element.EnumerateObject())
    {
        dict[property.Name] = property.Value.ValueKind switch
        {
            JsonValueKind.String => property.Value.GetString(),
            JsonValueKind.Number => property.Value.TryGetInt32(out int i) ? i : property.Value.GetDouble(),
            JsonValueKind.True => true,
            JsonValueKind.False => false,
            JsonValueKind.Object => ConvertToExpando(property.Value),
            JsonValueKind.Array => property.Value.EnumerateArray()
                .Select(e => ConvertToExpando(e) ?? (object)e.GetRawText())
                .ToList(),
            _ => null
        };
    }

    return expando;
}

Да, кода больше. Но зато вы получаете настоящий dynamic объект, с которым можно работать как с обычным. И никаких внешних библиотек.

Вариант B: Через JsonSerializer.Deserialize<object>

Можно десериализовать просто в object, но результат будет в виде словарей и списков — не так удобно, как dynamic.

var obj = JsonSerializer.Deserialize<object>(json);
// obj — это Dictionary<string, object> или List<object>

Это работает, но теряется «синтаксический сахар» точечной нотации.

Способ 3: Хак с System.Dynamic.ExpandoObject

Если вам не нужен полный кастомный конвертер, можно использовать ExpandoObject напрямую. Это как раз то, что «под капотом» у dynamic.

using System.Dynamic;
using Newtonsoft.Json;

string json = @"{""product"":""Ноутбук"",""price"":1200}";

// Десериализуем сразу в ExpandoObject
dynamic obj = JsonConvert.DeserializeObject<ExpandoObject>(json);

Console.WriteLine(obj.product); // Ноутбук
Console.WriteLine(obj.price);   // 1200

// Можно даже добавлять свойства на лету
obj.currency = "USD";
Console.WriteLine(obj.currency); // USD

Этот способ — золотая середина. Вы получаете dynamic, но с чуть большей производительностью, чем через object.

Способ 4: JSON Path — когда JSON очень глубокий

Иногда JSON похож на матрёшку: внутри объект, внутри ещё объект, а вам нужно достать одно значение на 5-м уровне вложенности. Тут dynamic может быть громоздким.

В Newtonsoft.Json есть SelectToken() с поддержкой JSON Path:

using Newtonsoft.Json.Linq;

string json = @"
{
    'store': {
        'book': [
            {'title': 'Война и мир', 'price': 500},
            {'title': 'Преступление и наказание', 'price': 450}
        ]
    }
}";

JObject obj = JObject.Parse(json);
var titles = obj.SelectTokens("$.store.book[*].title");

foreach (var title in titles)
{
    Console.WriteLine(title); // Война и мир, Преступление и наказание
}


                
                

Блоги

Рекомендаций по книгам