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); // Война и мир, Преступление и наказание
}