DEV Community

Anthony Ryckembusch
Anthony Ryckembusch

Posted on

JSON avec .Net

En lisant le post « Try the new System.Text.Json source generator« , je me suis rendu compte que j’utilisais que très peu l’espace de noms System.Text.Json du .Net. Des années a utiliser Newtonsoft.Json, que je ne pensais plus à tester ou utiliser autre chose. Aaahhh la routine, l’ennemi du changement.

Microsoft a livré cet espace de noms System.Text.Json avec le .Net Core 3.0 en 2019. Le pourquoi cet espace de noms était la performance et la sécurité. Microsoft a essayé d’empaqueter Newtonsoft.Json dans .Net, ils ont eu quelques difficultés. Microsoft souhaitait également supprimer la dépendance Newtonsoft.Json d’ASP.Net Core 3.0. Voilà pour la petite histoire.

J’ai fait un projet de test pour montrer cet espace de noms. Le projet se trouve sur GitHub, dans le répertoire dotNet6, projet TutoJson.

Lire un JSON

Que la source soit via une requête Web ou d’un fichier JSON, il faut « Deserializer » le JSON en objet. Pour l’exemple j’ai généré un fichier JSON avec le site : JSON Generator. J’ai crée des « Personnes ». En voici un exemple de personne.

{
    "id": "70f05566-1386-425d-bec3-d20b9c8e88fa",
    "index": 0,
    "isActive": true,
    "age": 40,
    "name": "Florence Wade",
    "gender": "female",
    "company": "VICON",
    "email": "florencewade@vicon.com",
    "phone": "+1 (942) 437-2165",
    "address": "405 Times Placez, Motley, Nevada, 9643",
    "about": "Id reprehenderit aute laborum do eiusmod qui reprehenderit dolore. Proident reprehenderit duis do duis minim commodo aute id. Anim incididunt in irure fugiat ipsum ex. Aliqua officia sunt excepteur proident tempor ex.\r\n",
    "registered": "2021-08-18T04:16:38 -02:00",
    "latitude": 8.848137,
    "longitude": 73.653237,
    "friends": [
      {
        "id": 0,
        "name": "Johnston Zamora"
      },
      {
        "id": 1,
        "name": "Clarke Blair"
      },
      {
        "id": 2,
        "name": "Lindsey Chandler"
      }
    ],
    "greeting": "Hello, Florence Wade! You have 2 unread messages.",
    "favoriteFruit": "apple"
}
Enter fullscreen mode Exit fullscreen mode

Voilà un bout de code, pour passer d’une forme « string » en objet.

"######## Début de l'application Démo ########".ToConsoleInfo();
"######## pour JSON en .Net 6         ########".ToConsoleInfo();
"#:> Appuyer sur une touche pour commencer.".ToConsoleInfo();
ReadKey();

"######## LECTURE d'un JSON à partir d'un fichier ###########".ToConsoleInfo();
"#:> Appuyer sur une touche pour commencer.".ToConsoleInfo();
ReadKey();
WriteLine();

string pathFile = Path.Combine(AppContext.BaseDirectory, "Files", "personnes.json");
// En utilisant un stream.
using(var stream = File.OpenRead(pathFile))
{
    List<Personne> personnes = await JsonSerializer.DeserializeAsync< /><Personne>>(stream);
    $"Il y a {personnes.Count} personnes dans le fichier.".ToConsoleResult();
}
Enter fullscreen mode Exit fullscreen mode

Il faut utiliser :

await JsonSerializer.DeserializeAsync<List<Personne>>(stream);

Doc Microsoft pour JsonSerializer.DeserializeAsync.

A partir de là, nous pouvons travailler avec des objets Personne, mais moi je veux faire de la « manipulation » sur le JSON, sans passer pour nos objets « métiers ».

JsonObject / JsonNode

Chargement en mémoire.

JsonNode jsonNodePerson;
using (var stream = File.OpenRead(pathFile))
{
    jsonNodePerson = JsonObject.Parse(stream);
}
//NOTE : Possible de passer par un string.
//string contentString = File.ReadAllText(pathFile, Encoding.UTF8);
Enter fullscreen mode Exit fullscreen mode

Faisons quelques requêtes. Trouver le nom d’une personne :

"Pour avoir le nom de la 2eme personne dans le fichier".ToConsoleInfo();
string nomPersonne = jsonNodePerson[1]["name"].GetValue<string>();
$"Nom de la 2eme personne {nomPersonne}".ToConsoleResult();

// avoir la valeur de la latitude.
double latitude = jsonNodePerson[1]["latitude"].GetValue<double>();
$"Latitude renseigné : {latitude}".ToConsoleResult();
Enter fullscreen mode Exit fullscreen mode

Il est possible de « naviguer » dans l’arbre du JSON. Et s’il y a un Array dans une propriété, pareil on peut naviguer dedans.

La propriété friends est un Array.

"Parcours dans les amis :".ToConsoleInfo();
var jsonFriend = jsonNodePerson[1]["friends"][2].ToJsonString();
$"Affiche un amis de la liste en JSON : {jsonFriend}".ToConsoleResult();

"Parcours dans un ami particulier :".ToConsoleInfo();
string nomAmi = jsonNodePerson[1]["friends"][2]["name"].GetValue<string>();
$"Nom de l'amis : {nomAmi}".ToConsoleResult();
Enter fullscreen mode Exit fullscreen mode

J’ai utiliser la méthode ToJsonString() qui : (je cite la doc) « Convertit l’instance actuelle en chaîne au format JSON ».

Bon maintenant je veux ajouter une nouvelle propriété à toutes les personnes, mais toujours sans passer par un objet Personne.

Se sera la propriété preference, et ils auront tous la même valeur : « .Net 6 le meilleur ».

"Ajout d'une nouvelle propriété pour tout le monde : preference, avec sa valeur".ToConsoleInfo();
"#:> Appuyer sur une touche pour commencer.".ToConsoleInfo();
ReadKey();
JsonArray jsonArray = jsonNodePerson.AsArray();
$"Combien d'élément dans JsonArray : {jsonArray.Count}".ToConsoleResult();
WriteLine();
"Nous allons ajouter une nouvelle propriété dans le JSON".ToConsoleInfo();
for (int i = 0; i < jsonArray.Count; i++)
{
    jsonArray[i]["preference"] = ".Net 6 le meilleur";
}

"Vérification sur une personnne au hasard :".ToConsoleInfo();
Random random = new Random();
jsonArray[random.Next(0, jsonArray.Count)].ToString().ToConsoleResult();
Enter fullscreen mode Exit fullscreen mode

J’indique que jsonNodePerson est un type JsonArray, ce qui me permet de boucler dessus :

JsonArray jsonArray = jsonNodePerson.AsArray();

Et pour chaque élément, j’ajoute la nouvelle propriété :

jsonArray[i]["preference"] = ".Net 6 le meilleur";

gif_to_explain_que_cest_simple

On peut dire que c’est très facile d’ajouter une propriété.

Même chose dans le sens inverse, je veux enlever la propriété gender.

"Maintenant il faut enlever le genre, propriété gender".ToConsoleInfo();
"#:> Appuyer sur une touche pour commencer.".ToConsoleInfo();
ReadKey();
for (int i = 0; i < jsonArray.Count; i++)
{
    jsonArray[i].AsObject().Remove("gender");
}
"Vérification sur une personnne au hasard :".ToConsoleInfo();
jsonArray[random.Next(0, jsonArray.Count)].ToString().ToConsoleResult();
Enter fullscreen mode Exit fullscreen mode

JsonDocument

Il est possible de charger un JSON dans un JsonDocument.

2 points d’attention sur JsonDocument :

Fournit un mécanisme permettant d’examiner le contenu structurel d’une valeur JSON sans instancier automatiquement des valeurs de données.Microsoft

Source : Microsoft

Ce qui veut dire un gain sur l’espace mémoire.

Et l’autre point, qui n’est pas des moindres :

Cette classe utilise les ressources de la mémoire regroupée pour réduire l’impact du garbage collector (GC) dans les scénarios à forte utilisation.
Si vous ne parvenez pas à supprimer correctement cet objet, la mémoire ne sera pas retournée au pool, ce qui augmentera l’impact GC sur les différentes parties de l’infrastructure.Microsoft

Source : Microsoft

Il faut utiliser les using(JsonDocument ....), et ne pas faire le fifou.

Avec JsonDocument ça nous permet de faire des requêtes LinQ.

"#### Utilisation avec JsonDocument ####".ToConsoleInfo();
"#:> Appuyer sur une touche pour commencer.".ToConsoleInfo();
ReadKey();
"Récupération de tous les noms".ToConsoleInfo();

using (var stream = File.OpenRead(pathFile))
using (JsonDocument document = JsonDocument.Parse(stream))
{
    JsonElement root = document.RootElement;
    var namesPersonne = root.EnumerateArray()
                            .Select(x => x.GetProperty("name")
                                        .GetString())
                            .ToList();

    $"Il y a {namesPersonne.Count} noms".ToConsoleResult();

    foreach (var name in namesPersonne.Take(100 .. 110))
    {
        $"- Nom : {name}".ToConsoleResult();
    }

    var personnesSup35ans = root.EnumerateArray()
                                .Where(x => x.GetProperty("age").GetInt32() > 35)
                                .Select(x => new
                                {
                                    Nom = x.GetProperty("name").GetString(),
                                    Age = x.GetProperty("age").GetInt32()
                                }).ToList();

    $"Il y a {personnesSup35ans.Count} personnes qui ont plus de 35 ans".ToConsoleResult();
    foreach (var person in personnesSup35ans)
    {
        $"- Nom : {person.Nom} - Age : {person.Age}.".ToConsoleResult();
    }
}
Enter fullscreen mode Exit fullscreen mode

Note : J’ai utilisé namesPersonne.Take(100..110), c’est une des nouveautés du C# 10. Permet de prendre les éléments entre l’index 100 et 110. Doc Microsoft sur le Take et le Range.

C# 10 est plein de petites nouveautés, que je vous invite à lire sur Announcing .Net 6.

Serialization

Maintenant nous allons « Serializer » une liste d’objet Incident. Il n’y a que 2 propriétés dans cet objet, Id et Titre.

List<Incident> incidentsObject = new List<Incident>();
for (int i = 0; i <= 10; i++)
{
    incidentsObject.Add(new Incident() { Id = i, Titre = $"Problème num {i}" });
}

string contentInString = JsonSerializer.Serialize(incidentsObject);

contentInString.ToConsoleResult();
WriteLine();

"Raahhh beurk, ce n'est pas indenté".ToConsoleInfo();
Enter fullscreen mode Exit fullscreen mode

Voilà la sortie.

[{"Id":0,"Titre":"Probl\u00E8me num 0"},{"Id":1,"Titre":"Probl\u00E8me num 1"},{"Id":2,"Titre":"Probl\u00E8me num 2"},{"Id":3,"Titre":"Probl\u00E8me num 3"},{"Id":4,"Titre":"Probl\u00E8me num 4"},{"Id":5,"Titre":"Probl\u00E8me num 5"},{"Id":6,"Titre":"Probl\u00E8me num 6"},{"Id":7,"Titre":"Probl\u00E8me num 7"},{"Id":8,"Titre":"Probl\u00E8me num 8"},{"Id":9,"Titre":"Probl\u00E8me num 9"},{"Id":10,"Titre":"Probl\u00E8me num 10"}]
Enter fullscreen mode Exit fullscreen mode

gif_disgusting

Quand vous exécuterez le code, le JSON est tout en ligne par défaut, ce qui est bien pour le stockage, envoie sur le Web, mais incompréhensible quand il faut l’ouvrir. Il est possible d’indiquer au moment de la sérialisation d’indenté le JSON avec JsonSerializerOptions.

JsonSerializerOptions options = new() { WriteIndented = true };
contentInString = JsonSerializer.Serialize(incidentsObject, options);
Enter fullscreen mode Exit fullscreen mode
{
  "description": "Liste des incidents pour un probl\u00E8me",
  "incidents": [
    {
      "id": 1,
      "titre": "Probl\u00E8me num\u00E9ro 1."
    },
    {
      "id": 2,
      "titre": "Probl\u00E8me num\u00E9ro 2."
    },
    {
      "id": 3,
      "titre": "Probl\u00E8me num\u00E9ro 3."
    },
    {
      "id": 4,
      "titre": "Probl\u00E8me num\u00E9ro 4."
    },
    {
      "id": 5,
      "titre": "Probl\u00E8me num\u00E9ro 5."
    },
    {
      "id": 6,
      "titre": "Probl\u00E8me num\u00E9ro 6."
    },
    {
      "id": 7,
      "titre": "Probl\u00E8me num\u00E9ro 7."
    },
    {
      "id": 8,
      "titre": "Probl\u00E8me num\u00E9ro 8."
    },
    {
      "id": 9,
      "titre": "Probl\u00E8me num\u00E9ro 9."
    },
    {
      "id": 10,
      "titre": "Probl\u00E8me num\u00E9ro 10."
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Bon, le JSON est indenté, mais les caractères spéciaux ne sont pas reconnus. Pour remédier à ça il faut encore ajouter une option.

JsonSerializerOptions optionSpecial = new()
{
    Encoder = JavaScriptEncoder.Create(UnicodeRanges.All),
    WriteIndented = true
};

contentInString = JsonSerializer.Serialize(incidentsObject, optionSpecial);
Enter fullscreen mode Exit fullscreen mode

Avec UnicodeRanges, il est possible de choisir l’encodage des caractères. –> Doc UnicodeRanges.

Je fais court sur cette partie, beaucoup d’explications pour la sérialisation sont sur le post :

« Try the new System.Text.Json source generator » que j’ai parlé au début.

Création d’un JSON à la « mano »

Bon maintenant, je veux pouvoir créer un JSON de toute pièce, sans objet !

Création du JSON Incident.

JsonArray incidents = new JsonArray();
for (int id = 1; id <= 10; id++)
{
    JsonObject nouveauIncident = new JsonObject
    { 
        ["id"] = id, 
        ["titre"] = $"Problème numéro {id}." 
    };
    incidents.Add(nouveauIncident);
}

// Ajout de tous les incidents dans 
JsonObject jsonIncidents = new JsonObject();
jsonIncidents.Add("description", "Liste des incidents pour un problème");
jsonIncidents.Add("incidents", incidents);
Enter fullscreen mode Exit fullscreen mode

Dans cette exemple, j’ai voulu montrer les 2 manières de créer une structure JSON :

  • en utilisant les [ ] dans l’initialisation d’un nouveau JsonObject
  • ou en passant pour la méthode .Add("propertyName", Value)
{
  "description": "Liste des incidents pour un problème",
  "incidents": [
    {
      "id": 1,
      "titre": "Problème numéro 1."
    },
    {
      "id": 2,
      "titre": "Problème numéro 2."
    },
    {
      "id": 3,
      "titre": "Problème numéro 3."
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Comme nous travaillons avec des objets JsonObject, JsonNode, JsonArray, nous pouvons faire toutes les manipulations du début du post.

Pour ce qui est de la performance, il existe plusieurs posts sur le Web mais sans trop vous « spoiler », c’est le meilleur !

Top comments (0)