1. Introduction
Dans les leçons précédentes, on travaillait toujours avec des structures strictement typées. Par exemple, on a une classe Person qu'on sérialise en JSON et inversement :
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
Mais parfois vous ne connaissez pas la structure des données à l'avance. Par exemple :
- Vous écrivez un parser pour un service externe où la structure de la réponse n'est pas fixe.
- Vous devez extraire seulement une partie de l'information, pas forcément remplir une classe complète.
- Il faut éditer ou générer du JSON "à la volée" en fonction de conditions dynamiques.
C'est là que les "structures dynamiques" entrent en jeu — des objets qui conservent l'arbre JSON comme un ensemble de paires clé/valeur, sans nécessiter une classe C# décrite à l'avance.
Pourquoi on utilise le plus souvent Newtonsoft.Json
En .NET il y a deux acteurs principaux pour travailler avec JSON :
- System.Text.Json — la bibliothèque intégrée de Microsoft (évolue depuis .NET Core 3.0).
- Newtonsoft.Json (Json.NET) — une librairie populaire qui a donné au monde C# des classes comme JObject et JArray.
Au moment de la rédaction de la leçon, System.Text.Json n'offre pas encore un équivalent complet de JObject/JArray avec la même ergonomie. Donc, si vous devez souvent analyser ou modifier des structures JSON complexes/inconnues, on choisit souvent Newtonsoft.Json.
Types principaux : JObject, JArray, JValue et la famille JToken
- JToken — type de base pour tous les nœuds JSON.
- JObject — objet JSON { ... }, un ensemble de paires "clé-valeur".
- JArray — tableau [ ... ].
- JValue — valeur individuelle (42, "texte", true, null).
L'idée est simple : on parse le JSON — on obtient un "arbre" de ces tokens et on peut le parcourir, chercher, modifier, supprimer et ajouter des éléments.
2. Lire un JSON inconnu
Supposons qu'on a reçu ce JSON et qu'on ne veut pas ou ne peut pas écrire une classe à l'avance :
{
"status": "ok",
"amount": 150.5,
"items": [
{
"name": "book",
"qty": 1
},
{
"name": "pen",
"qty": 3
}
]
}
Avec Newtonsoft.Json on peut le transformer en arbre et l'explorer à la volée.
Exemple : lecture JSON dans un JObject
using Newtonsoft.Json.Linq;
string json = @"{
""status"": ""ok"",
""amount"": 150.5,
""items"": [
{ ""name"": ""book"", ""qty"": 1 },
{ ""name"": ""pen"", ""qty"": 3 }
]
}";
// On parse la chaîne et on obtient l'arbre
JObject root = JObject.Parse(json);
// On prend les propriétés comme dans un dictionnaire
string status = (string)root["status"]; // "ok"
double amount = (double)root["amount"]; // 150.5
// items — c'est un tableau, donc JArray
JArray items = (JArray)root["items"];
// On parcourt le tableau
foreach (JObject item in items)
{
string name = (string)item["name"];
int qty = (int)item["qty"];
Console.WriteLine($"Produit: {name}, Qté: {qty}");
}
Très pratique : pas besoin de déclarer des classes — on peut extraire rapidement les morceaux nécessaires.
3. Indexeurs et accès dynamique
Indexeurs :
- Pour un objet : root["status"], root["items"]
- Pour un tableau : items[0], items[1]
Pour obtenir la valeur directement dans le type souhaité, utilisez le cast (string), (int), (bool), (double) — la librairie convertit automatiquement le type.
Si les données peuvent manquer, faites attention : l'accès à une clé absente renverra null, et un cast explicite échouera. Mieux vaut utiliser des méthodes avec vérification :
if (root.TryGetValue("amount", out var token))
{
double amount = token.Value<double>();
// Cette manière est plus pratique : Value<T>() convertit directement
}
Les objets et tableaux imbriqués se lisent aussi simplement :
// Obtenir le deuxième produit
JObject secondItem = (JObject)root["items"][1];
string itemName = (string)secondItem["name"]; // "pen"
On peut aussi utiliser l'accès dynamique :
dynamic droot = root;
Console.WriteLine(droot.status); // "ok"
Mais rappelez-vous : avec dynamic le compilateur ne vérifie pas l'accès aux champs — les erreurs surgissent seulement à l'exécution.
4. Modifier l'arbre JSON à la volée
Ajouter des éléments
root["currency"] = "RUB"; // Ajout d'une nouvelle propriété
items.Add(new JObject
{
["name"] = "eraser",
["qty"] = 2
});
Modifier et supprimer
root["status"] = "done"; // Modification d'une valeur
items[0]["qty"] = 5; // Augmenté la quantité du premier produit
items.RemoveAt(1); // Supprimé le deuxième produit
Enregistrer le résultat en chaîne
string modifiedJson = root.ToString();
// Ou root.ToString(Formatting.Indented) pour l'esthétique
5. Créer une structure JSON à partir de rien
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
}
Utile si vous devez renvoyer à une API externe seulement une partie des données ou construire du JSON selon des conditions.
6. Comment construire votre objet à partir d'un JObject
Parfois il faut transformer un JSON flexible en un objet C# strict. Options :
- Désérialisation classique : JsonConvert.DeserializeObject<MyClass>(...).
- Construction manuelle de l'objet en extrayant les champs nécessaires depuis JObject.
class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
// Supposons qu'on a reçu ce JSON :
string incoming = @"{ ""name"":""Bob"", ""age"":30, ""extraField"":true }";
JObject j = JObject.Parse(incoming);
// On construit l'objet manuellement — seulement les champs nécessaires
var person = new Person
{
Name = (string)j["name"],
Age = (int)j["age"],
// extraField on n'en a pas besoin — et c'est parfait !
};
7. Exemples d'erreurs et subtilités : pièges typiques
Travailler avec des structures JSON dynamiques est flexible, mais il y a des "pièges" :
- Accéder à une clé/élément inexistant renvoie null ; un cast explicite vers un type valeur lancera une exception.
- Mismatch de type attendu (on attend un objet mais on reçoit une valeur) — erreur de cast.
- Pour parcourir les champs d'un objet utilisez Properties() :
foreach (var prop in root.Properties())
{
Console.WriteLine($"Champ: {prop.Name}, Valeur: {prop.Value}");
}
- Avec Newtonsoft.Json il est pratique d'utiliser des requêtes de type LINQ (filtrage, recherche) :
var expensiveItems = items.Where(obj => (int)obj["qty"] > 2);
foreach (var item in expensiveItems)
Console.WriteLine(item);
8. Erreurs typiques quand on travaille avec JObject/JArray
Erreur n°1 : absence du champ attendu ou mismatch de type. Très souvent le dev suppose qu'un champ existe dans l'objet, mais soit il est absent, soit il est d'un autre type. Si on attend un objet et qu'on reçoit un nombre, un cast provoquera une exception. Vérifiez la présence et le type avant d'utiliser.
Erreur n°2 : accéder aux champs des structures imbriquées sans vérifier le null. Quand le JSON contient des objets imbriqués, et que les clés diffèrent ou sont absentes, tenter d'accéder à un champ manquant peut provoquer un plantage. Vérifiez le null avant de lire les valeurs des nœuds imbriqués.
Erreur n°3 : caster vers un type valeur quand la valeur est null. Si la clé existe mais que sa valeur est null, une expression comme (int)j["age"] lèvera une exception. Utilisez Value<T>() — il renverra la valeur par défaut (pour int c'est 0, pour les string — null).
Erreur n°4 : abus de l'utilisation d'objets dynamiques au lieu de modèles clairs. Pour des structures trop complexes, il est souvent plus sûr et lisible de décrire des classes C# : ça réduit les bugs et améliore la lecture du code. Utilisez la dynamique quand la structure est vraiment inconnue ou très variable.
Maintenant vous savez comment parser, modifier et créer rapidement des structures JSON avec JObject et JArray, même sans modèles prédéfinis.
GO TO FULL VERSION