Cours JavaScript et TypeScript · Module 4: JavaScript asynchrone
Traitement des données JSON et gestion des structures de données complexes
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 :
JSON.parse()- convertit une chaîne JSON en objet JavaScriptJSON.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 :
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
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
- Filtrer les données avec
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. Que fait la méthode JSON.parse() ?
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