Cours JavaScript et TypeScript · Module 4: JavaScript asynchrone

Traitement des données JSON et gestion des structures de données complexes

10 min de lecture
Dans cette leçon4

Dans les exercices précédents, nous avons appris à récupérer des données depuis des serveurs API à l'aide de Fetch et à créer des clients HTTP avancés pour notre Parc Jurassique. Il est temps maintenant d'apprendre à traiter, transformer et gérer efficacement les structures de données JSON complexes que nous recevons de nos systèmes.

JSON (JavaScript Object Notation) est le format standard d'échange de données - léger, lisible par les humains et facile à traiter pour les machines. Au Parc Jurassique, nous utilisons JSON pour représenter tout - des données sur les dinosaures aux états des systèmes de sécurité, en passant par les enregistrements des visiteurs.

Bases du JSON en JavaScript

En JavaScript, JSON est représenté par deux principaux types de structures :

  • Les objets (objets JavaScript / hashmaps)
  • Les tableaux (listes d'éléments)

Exemple de structure JSON représentant un dinosaure dans notre parc :

1const trex = {
2  "id": "dino-001",
3  "name": "Rexy",
4  "species": "Tyrannosaurus Rex",
5  "age": 25,
6  "stats": {
7    "weight": 8000,
8    "length": 12.3,
9    "height": 3.6
10  },
11  "diet": ["meat", "carrion"],
12  "threatLevel": 5,
13  "location": {
14    "enclosure": "T-Rex Kingdom",
15    "coordinates": {
16      "x": 34.5,
17      "y": 67.2
18    }
19  },
20  "behaviors": [
21    {
22      "type": "hunting",
23      "frequency": "high",
24      "times": ["morning", "evening"]
25    },
26    {
27      "type": "resting",
28      "frequency": "medium",
29      "times": ["midday"]
30    }
31  ],
32  "addedOn": "2023-06-15T08:30:00Z",
33  "active": true
34}

Parser et sérialiser du JSON

JavaScript propose deux méthodes fondamentales pour travailler avec JSON :

  1. JSON.parse() - convertit une chaîne JSON en objet JavaScript
  2. JSON.stringify() - convertit un objet JavaScript en chaîne JSON
1// Sérialiser l'objet en chaîne JSON
2const trexString = JSON.stringify(trex);
3console.log("T-Rex sérialisé :", trexString);
4
5// Parser la chaîne JSON pour la reconvertir en objet
6const parsedTrex = JSON.parse(trexString);
7console.log("T-Rex parsé :", parsedTrex);

Paramètres supplémentaires de JSON.stringify() et JSON.parse()

JSON.stringify() possède des paramètres supplémentaires qui peuvent être utiles :

1// Formater le JSON avec indentation pour la lisibilité
2const prettyTrex = JSON.stringify(trex, null, 2);
3console.log("T-Rex formaté :");
4console.log(prettyTrex);
5
6// Sérialiser de façon sélective uniquement certains champs
7const simpleTrex = JSON.stringify(trex, ['id', 'name', 'species', 'age']);
8console.log("T-Rex simplifié :", simpleTrex);
9
10// Fonction de remplacement personnalisée
11const customTrex = JSON.stringify(trex, (key, value) => {
12  // Masquer les coordonnées exactes pour des raisons de sécurité
13  if (key === 'coordinates') return "HIDDEN";
14  // Convertir les codes du niveau de menace en description textuelle
15  if (key === 'threatLevel') {
16    const descriptions = ["harmless", "low", "moderate", "high", "extreme"];
17    return descriptions[value - 1] || value;
18  }
19  return value;
20});
21console.log("T-Rex avec sérialisation personnalisée :", customTrex);

Techniques de traitement des structures JSON complexes

Supposons que nous ayons reçu une structure JSON complexe contenant des données sur tous les dinosaures du parc. Comment traiter efficacement de telles données ?

1. Filtrer les données

Le filtrage est l'une des opérations les plus courantes lorsqu'on travaille avec des collections de données :

1// Supposons que nous ayons un tableau de dinosaures
2const dinosaurs = await api.get('/dinosaurs');
3
4// Filtrer uniquement les prédateurs
5const predators = dinosaurs.filter(dino => dino.diet.includes('meat'));
6
7// Filtrer les dinosaures d'un enclos spécifique
8const dinosInEnclosure = dinosaurs.filter(dino =>
9  dino.location.enclosure === 'T-Rex Kingdom'
10);
11
12// Filtrer les dinosaures dangereux (niveau de menace > 3)
13const dangerousDinos = dinosaurs.filter(dino => dino.threatLevel > 3);
14
15// Filtrage complexe avec plusieurs conditions
16const dangerousPredatorsInZoneA = dinosaurs.filter(dino =>
17  dino.threatLevel > 3 &&
18  dino.diet.includes('meat') &&
19  dino.location.enclosure === 'Zone A'
20);

2. Mapper et transformer les données

Nous devons souvent transformer des données d'un format à un autre, par exemple pour les afficher dans une interface utilisateur :

1// Créer une liste de dinosaures simplifiée pour l'UI
2const simpleDinos = dinosaurs.map(dino => ({
3  id: dino.id,
4  name: dino.name,
5  species: dino.species,
6  threatLevel: dino.threatLevel
7}));
8
9// Ajouter de nouveaux champs basés sur les données existantes
10const dinosWithExtras = dinosaurs.map(dino => ({
11  ...dino, // copier tous les champs existants
12  ageYears: dino.age, // renommer le champ
13  threatDescription: ['harmless', 'low', 'moderate', 'high', 'extreme'][dino.threatLevel - 1],
14  weightTons: dino.stats.weight / 1000,
15  needsGuard: dino.threatLevel > 3
16}));
17
18// Transformer dans un format spécifique pour un graphique
19const chartData = dinosaurs.map(dino => ({
20  x: dino.stats.length,
21  y: dino.stats.weight,
22  r: dino.threatLevel * 5, // taille du point proportionnelle à la menace
23  label: dino.name,
24  color: dino.diet.includes('meat') ? 'red' : 'green'
25}));

3. Agrégation et réduction des données

La réduction permet d'agréger les données pour obtenir des résumés et des statistiques :

1// Calculer le poids moyen de tous les dinosaures
2const avgWeight = dinosaurs.reduce((sum, dino) => sum + dino.stats.weight, 0) / dinosaurs.length;
3
4// Trouver le dinosaure le plus long
5const longest = dinosaurs.reduce((longest, dino) =>
6  dino.stats.length > longest.stats.length ? dino : longest
7, dinosaurs[0]);
8
9// Grouper les dinosaures par espèce
10const dinosBySpecies = dinosaurs.reduce((groups, dino) => {
11  // Si le groupe pour cette espèce n'existe pas, le créer
12  if (!groups[dino.species]) {
13    groups[dino.species] = [];
14  }
15  // Ajouter le dinosaure au groupe approprié
16  groups[dino.species].push(dino);
17  return groups;
18}, {});
19
20// Compter les dinosaures par niveau de menace
21const countByThreat = dinosaurs.reduce((counter, dino) => {
22  counter[dino.threatLevel] = (counter[dino.threatLevel] || 0) + 1;
23  return counter;
24}, {});

4. Chaîner les opérations

Les méthodes de tableau peuvent être chaînées, ce qui permet un code concis et lisible :

1// Trouver les 3 dinosaures les plus dangereux de la Zone A
2const top3DangerousInZoneA = dinosaurs
3  .filter(dino => dino.location.enclosure === 'Zone A')
4  .sort((a, b) => b.threatLevel - a.threatLevel) // tri décroissant
5  .slice(0, 3) // ne prendre que les 3 premiers
6  .map(dino => ({
7    name: dino.name,
8    species: dino.species,
9    threatLevel: dino.threatLevel
10  }));
11
12// Calculer le poids moyen par espèce
13const avgWeightBySpecies = Object.entries(
14  dinosaurs.reduce((groups, dino) => {
15    if (!groups[dino.species]) {
16      groups[dino.species] = { sum: 0, count: 0 };
17    }
18    groups[dino.species].sum += dino.stats.weight;
19    groups[dino.species].count += 1;
20    return groups;
21  }, {})
22).map(([species, { sum, count }]) => ({
23  species,
24  avgWeight: sum / count
25}));

5. Recherche et mise à jour profondes des données

Dans les structures complexes, nous devons souvent trouver ou mettre à jour des données profondément imbriquées :

1// Trouver un dinosaure selon une condition profondément imbriquée
2const dinoNearCoordinates = dinosaurs.find(dino => {
3  const { x, y } = dino.location.coordinates;
4  // Trouver un dinosaure dans un rayon de 5 unités autour du point (30, 70)
5  return Math.sqrt(Math.pow(x - 30, 2) + Math.pow(y - 70, 2)) < 5;
6});
7
8// Mettre à jour une propriété profondément imbriquée (immutabilité)
9function updateDinoLocation(dino, newX, newY) {
10  return {
11    ...dino,
12    location: {
13      ...dino.location,
14      coordinates: {
15        ...dino.location.coordinates,
16        x: newX,
17        y: newY
18      }
19    }
20  };
21}
22
23// Utilisation
24const updatedDino = updateDinoLocation(trex, 35.0, 68.0);

6. Traitement récursif des données

Certaines structures de données sont hiérarchiques ou ont une profondeur d'imbrication inconnue. Le traitement récursif est utile dans ces cas :

1// Exemple de structure hiérarchique de catégories de dinosaures
2const dinosaurCategories = {
3  "name": "Dinosaurs",
4  "subcategories": [
5    {
6      "name": "Theropods",
7      "subcategories": [
8        {
9          "name": "Tyrannosauroids",
10          "dinosaurs": [
11            { "id": "dino-001", "name": "Rexy" },
12            { "id": "dino-002", "name": "Tara" }
13          ]
14        },
15        {
16          "name": "Deinonychosaurs",
17          "dinosaurs": [
18            { "id": "dino-003", "name": "Blue" },
19            { "id": "dino-004", "name": "Delta" }
20          ]
21        }
22      ]
23    },
24    {
25      "name": "Sauropods",
26      "dinosaurs": [
27        { "id": "dino-005", "name": "Long" },
28        { "id": "dino-006", "name": "Tall" }
29      ]
30    }
31  ]
32};
33
34// Fonction récursive pour trouver un dinosaure par son ID dans la hiérarchie
35function findDinosaurById(node, id) {
36  // Vérifier si le nœud courant possède un tableau dinosaurs
37  if (node.dinosaurs) {
38    const found = node.dinosaurs.find(dino => dino.id === id);
39    if (found) return found;
40  }
41
42  // Si le nœud possède des sous-catégories, les parcourir récursivement
43  if (node.subcategories) {
44    for (const subcategory of node.subcategories) {
45      const found = findDinosaurById(subcategory, id);
46      if (found) return found;
47    }
48  }
49
50  return null;
51}
52
53// Fonction récursive pour aplatir la hiérarchie en une liste de tous les dinosaures
54function getAllDinosaurs(node, list = []) {
55  if (node.dinosaurs) {
56    list.push(...node.dinosaurs);
57  }
58
59  if (node.subcategories) {
60    for (const subcategory of node.subcategories) {
61      getAllDinosaurs(subcategory, list);
62    }
63  }
64
65  return list;
66}
67
68// Utilisation
69const blue = findDinosaurById(dinosaurCategories, "dino-003");
70console.log("Dinosaure trouvé :", blue);
71
72const allDinos = getAllDinosaurs(dinosaurCategories);
73console.log("Tous les dinosaures :", allDinos);

Exemple pratique : système de surveillance des dinosaures

Combinons maintenant toutes les techniques abordées dans un exemple pratique. Disons que nous construisons un tableau de bord pour le responsable du Parc Jurassique, montrant l'état de tous les dinosaures du parc.

1// Fonction pour récupérer les données depuis l'API
2async function fetchDinosaurData() {
3  try {
4    const response = await fetch('https://api.jurassic-park.com/dinosaurs');
5    if (!response.ok) throw new Error(`Erreur HTTP : ${response.status}`);
6    return await response.json();
7  } catch (error) {
8    console.error('Erreur lors de la récupération des données :', error);
9    return { dinosaurs: [] };
10  }
11}
12
13// Fonction pour traiter et préparer les données pour l'affichage
14function processDataForDisplay(data) {
15  const { dinosaurs } = data;
16
17  // Statistiques agrégées
18  const stats = {
19    count: dinosaurs.length,
20    speciesCount: new Set(dinosaurs.map(d => d.species)).size,
21    avgWeight: dinosaurs.reduce((sum, d) => sum + d.stats.weight, 0) / dinosaurs.length,
22    maxThreat: Math.max(...dinosaurs.map(d => d.threatLevel))
23  };
24
25  // Grouper par enclos
26  const dinosByEnclosure = dinosaurs.reduce((groups, dino) => {
27    const enclosure = dino.location.enclosure;
28    if (!groups[enclosure]) groups[enclosure] = [];
29    groups[enclosure].push(dino);
30    return groups;
31  }, {});
32
33  // Compter par niveau de menace
34  const countByThreat = [1, 2, 3, 4, 5].map(level => ({
35    level,
36    count: dinosaurs.filter(d => d.threatLevel === level).length,
37    percent: (dinosaurs.filter(d => d.threatLevel === level).length / dinosaurs.length) * 100
38  }));
39
40  // Identifier les menaces potentielles
41  const threats = dinosaurs
42    .filter(d => d.threatLevel >= 4)
43    .map(d => ({
44      id: d.id,
45      name: d.name,
46      species: d.species,
47      enclosure: d.location.enclosure,
48      threatLevel: d.threatLevel,
49      lastBehavior: d.behaviors[0]?.type || 'unknown',
50      coordinates: d.location.coordinates
51    }))
52    .sort((a, b) => b.threatLevel - a.threatLevel);
53
54  // Préparer les données pour le graphique de poids
55  const weightChartData = dinosaurs
56    .filter(d => d.stats.weight > 0)
57    .map(d => ({
58      id: d.id,
59      name: d.name,
60      species: d.species,
61      weight: d.stats.weight,
62      predator: d.diet.includes('meat'),
63      pointSize: Math.sqrt(d.stats.weight) / 5
64    }));
65
66  // Activités récentes (triées par ID)
67  const recentActivities = dinosaurs
68    .filter(d => d.behaviors && d.behaviors.length > 0)
69    .flatMap(d => d.behaviors.map(b => ({
70      dinoId: d.id,
71      dinoName: d.name,
72      type: b.type,
73      frequency: b.frequency,
74      times: b.times
75    })))
76    .sort((a, b) => a.dinoId.localeCompare(b.dinoId));
77
78  return {
79    stats,
80    dinosByEnclosure,
81    countByThreat,
82    threats,
83    weightChartData,
84    recentActivities,
85    raw: dinosaurs // Données originales si nécessaire
86  };
87}
88
89// Fonction pour afficher les données dans l'interface utilisateur (exemple)
90function renderDashboard(processedData) {
91  console.log("Données traitées pour le tableau de bord :", processedData);
92
93  // Exemple : afficher le tableau des menaces
94  const threatsTable = document.getElementById('threats-table');
95  if (threatsTable) {
96    threatsTable.innerHTML = processedData.threats
97      .map(t => `
98        <tr>
99          <td>${t.name}</td>
100          <td>${t.species}</td>
101          <td>${t.enclosure}</td>
102          <td class="threat-${t.threatLevel}">${t.threatLevel}</td>
103          <td>${t.lastBehavior}</td>
104          <td>
105            <button onclick="showOnMap(${t.coordinates.x}, ${t.coordinates.y})">
106              Afficher sur la carte
107            </button>
108          </td>
109        </tr>
110      `)
111      .join('');
112  }
113}
114
115// Fonction principale de mise à jour
116async function updateDashboard() {
117  const data = await fetchDinosaurData();
118  const processedData = processDataForDisplay(data);
119  renderDashboard(processedData);
120}
121
122// Lancer la mise à jour toutes les 5 minutes
123updateDashboard();
124setInterval(updateDashboard, 5 * 60 * 1000);

Résumé

Dans cet exercice, nous avons appris :

  1. Les bases de la sérialisation et de la désérialisation JSON - comment convertir les données entre le format texte et les objets JavaScript

  2. Des techniques avancées de traitement des données :

    • Filtrer les données avec .filter()
    • Transformer les données avec .map()
    • Agréger les données avec .reduce()
    • Chaîner les opérations
    • Travailler avec des structures profondément imbriquées
    • Traiter récursivement des données hiérarchiques
  3. L'application pratique de ces techniques dans le contexte d'un système de gestion de parc à dinosaures

La capacité à traiter et analyser efficacement les données JSON est cruciale pour tout projet JavaScript moderne - des simples sites web aux applications complexes comme le Jurassic Park Management System. Grâce aux techniques que tu as apprises, tu pourras transformer des données brutes en informations utiles qui aident à faire fonctionner le parc en douceur et en toute sécurité... du moins jusqu'à ce qu'un raptor trouve une faille dans le système !

Code de cette leçon : index.js
1// Traitement des données JSON - Parc Jurassique
2// Pratique: filtrage, mapping, réduction, récursion
3
4// === Données des dinosaures dans le parc ===
5const dinosaury = [
6  {
7    id: "dino-001",
8    name: "Rexy",
9    species: "Tyrannosaurus Rex",
10    age: 25,
11    statystyki: { waga: 8000, dlugosc: 12.3, wysokosc: 3.6 },
12    diety: ["viande", "charogne"],
13    zagrozenie: 5,
14    lokalizacja: { paddock: "T-Rex Kingdom", koordynaty: { x: 34.5, y: 67.2 } },
15    zachowania: [
16      { typ: "chasse", czestosc: "élevée", pory: ["matin", "soir"] }
17    ],
18    aktywny: true
19  },
20  {
21    id: "dino-002",
22    name: "Blue",
23    species: "Velociraptor",
24    age: 8,
25    statystyki: { waga: 150, dlugosc: 3.0, wysokosc: 1.8 },
26    diety: ["viande"],
27    zagrozenie: 4,
28    lokalizacja: { paddock: "Raptor Paddock", koordynaty: { x: 50.1, y: 30.4 } },
29    zachowania: [
30      { typ: "chasse", czestosc: "élevée", pory: ["matin", "nuit"] },
31      { typ: "entraînement", czestosc: "moyenne", pory: ["midi"] }
32    ],
33    aktywny: true
34  },
35  {
36    id: "dino-003",
37    name: "Brachio",
38    species: "Brachiosaurus",
39    age: 40,
40    statystyki: { waga: 56000, dlugosc: 25.0, wysokosc: 13.0 },
41    diety: ["plantes"],
42    zagrozenie: 1,
43    lokalizacja: { paddock: "Herbivore Valley", koordynaty: { x: 20.0, y: 45.0 } },
44    zachowania: [
45      { typ: "pâturage", czestosc: "élevée", pory: ["matin", "midi", "soir"] }
46    ],
47    aktywny: true
48  },
49  {
50    id: "dino-004",
51    name: "Spike",
52    species: "Triceratops",
53    age: 15,
54    statystyki: { waga: 9000, dlugosc: 9.0, wysokosc: 3.0 },
55    diety: ["plantes"],
56    zagrozenie: 2,
57    lokalizacja: { paddock: "Herbivore Valley", koordynaty: { x: 22.5, y: 42.1 } },
58    zachowania: [
59      { typ: "pâturage", czestosc: "élevée", pory: ["matin", "midi"] },
60      { typ: "défense", czestosc: "faible", pory: ["nuit"] }
61    ],
62    aktywny: true
63  },
64  {
65    id: "dino-005",
66    name: "Delta",
67    species: "Velociraptor",
68    age: 6,
69    statystyki: { waga: 130, dlugosc: 2.8, wysokosc: 1.6 },
70    diety: ["viande"],
71    zagrozenie: 4,
72    lokalizacja: { paddock: "Raptor Paddock", koordynaty: { x: 51.0, y: 31.0 } },
73    zachowania: [
74      { typ: "chasse", czestosc: "élevée", pory: ["nuit"] }
75    ],
76    aktywny: true
77  }
78];
79
80// === TODO: Exercices à réaliser ===
81
82// 1. Sérialisation et désérialisation JSON
83const jsonString = JSON.stringify(dinosaury, null, 2);
84console.log("=== Sérialisation JSON ===");
85console.log("Nombre de caractères:", jsonString.length);
86const parsedDino = JSON.parse(jsonString);
87console.log("Dinosaures parsés:", parsedDino.length);
88
89// 2. TODO: Filtering - trouve tous les prédateurs
90const drapiezniki = dinosaury.filter(d => d.diety.includes("viande"));
91console.log("\n=== Prédateurs ===");
92drapiezniki.forEach(d => console.log(d.name, "-", d.species));
93
94// 3. TODO: Mapping - crée une liste simplifiée
95const listaUproszczona = dinosaury.map(d => ({
96  name: d.name,
97  species: d.species,
98  waga_ton: d.statystyki.waga / 1000,
99  niebezpieczny: d.zagrozenie >= 4
100}));
101console.log("\n=== Liste simplifiée ===");
102console.log(JSON.stringify(listaUproszczona, null, 2));
103
104// 4. TODO: Reduction - calculate le poids moyen
105const sredniaWaga = dinosaury.reduce(
106  (suma, d) => suma + d.statystyki.waga, 0
107) / dinosaury.length;
108console.log("\n=== Poids moyen ===");
109console.log(sredniaWaga.toFixed(1), "kg");
110
111// 5. TODO: Groupement par paddocks
112const poPaddockach = dinosaury.reduce((grupy, d) => {
113  const paddock = d.lokalizacja.paddock;
114  if (!grupy[paddock]) grupy[paddock] = [];
115  grupy[paddock].push(d.name);
116  return grupy;
117}, {});
118console.log("\n=== Dinosaures dans les paddocks ===");
119Object.entries(poPaddockach).forEach(([paddock, names]) =>
120  console.log(paddock + ":", names.join(", "))
121);
122
123// 6. TODO: Chaînage de méthodes - top 3 des plus lourds
124const top3 = dinosaury
125  .sort((a, b) => b.statystyki.waga - a.statystyki.waga)
126  .slice(0, 3)
127  .map(d => d.name + " (" + (d.statystyki.waga / 1000) + "t)");
128console.log("\n=== Top 3 des plus lourds ===");
129top3.forEach((info, i) => console.log((i + 1) + ".", info));
130
131// 7. Add ton propre traitement de données !
132// Par ex. trouve le dinosaure au corps le plus long, compte les prédateurs vs les herbivores, etc.

Tu as repéré une erreur dans cette leçon ?

Teste tes connaissances

Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.

  1. 1. Que fait la méthode JSON.parse() ?

  2. 2. Que fait le troisième argument dans JSON.stringify(obj, null, 2) ?

Tâches pratiques dans le jeu

  • Clique dans l'ordre

    Ordonne les éléments de récupération et de parsing du JSON depuis une API :

  • Tri horizontal

    Ordonne les éléments de la création d'une Promise :

  • Tri horizontal

    Ordonne les éléments de la gestion d'une Promise avec then :

  • Tri vertical

    Try-catch

Articles utiles