Séance 12 — JSON et les erreurs

Sauvegarder des données, et survivre aux imprévus

  • Transformer un objet en texte et l’inverse
  • Sauvegarder des données dans le navigateur
  • Attraper une erreur avec try / catch au lieu de planter

1. Le problème du transport

Vos objets vivent dans la mémoire du navigateur. Rechargez la page : tout disparaît.

Pour sauvegarder des données, ou pour les envoyer à travers le réseau, il faut les transformer en texte. Car un fichier, un message réseau, une base de données : tout ça ne sait manipuler que du texte.

Le format universel pour faire ça s’appelle JSON (JavaScript Object Notation).

// Un objet JavaScript (vivant en mémoire)
const etudiant = { prenom: "Camille", age: 19 };

// Le même, en JSON (du texte)
'{"prenom":"Camille","age":19}'
NotePourquoi c’est important

JSON est le format d’échange du web. Quand votre application météo affiche la température, quand Instagram charge des photos, quand vous consultez un horaire de train : du JSON circule.

Ce que vous apprenez ici sert partout, dans tous les langages.

2. Les deux fonctions

const etudiant = { prenom: "Camille", age: 19 };

// Objet  →  texte
const texte = JSON.stringify(etudiant);
console.log(texte);         // {"prenom":"Camille","age":19}
console.log(typeof texte);  // "string"

// Texte  →  objet
const retour = JSON.parse(texte);
console.log(retour.prenom); // "Camille"
console.log(typeof retour); // "object"
Fonction Sens Direction
JSON.stringify(objet) transforme en chaîne objet → texte
JSON.parse(texte) analyse texte → objet
AstuceComment ne plus les confondre

stringify contient string : il fabrique une chaîne. parse veut dire « analyser » : il lit une chaîne pour en tirer un objet.

ImportantJSON n’est PAS du JavaScript

Ça se ressemble beaucoup, mais les règles sont plus strictes.

{
  "prenom": "Camille",
  "age": 19,
  "notes": [12, 15]
}
Règle JSON En JavaScript
Les noms sont entre "…" Facultatif
Guillemets doubles uniquement '…' accepté
Pas de virgule après le dernier élément Tolérée
Pas de commentaires // accepté
Pas de fonctions Autorisées

Ces différences sont la cause n°1 des erreurs de JSON.parse.

Exercice 12.1

const config = { theme: "sombre", taille: 14, notifications: true };
  1. Transformez-la en JSON et affichez le texte
  2. Reconstruisez un objet à partir de ce texte
  3. Affichez typeof à chaque étape
app.js
const config = { theme: "sombre", taille: 14, notifications: true };
console.log(typeof config); // "object"

const texte = JSON.stringify(config);
console.log(texte);         // {"theme":"sombre","taille":14,"notifications":true}
console.log(typeof texte);  // "string"

const retour = JSON.parse(texte);
console.log(retour.theme);  // "sombre"
console.log(typeof retour); // "object"

Astuce lisibilité : JSON.stringify(config, null, 2) produit un JSON indenté sur plusieurs lignes, bien plus agréable à lire dans la console.

3. Sauvegarder pour de vrai : localStorage

Le navigateur offre un petit coffre-fort par site : localStorage. Il survit à la fermeture du navigateur.

localStorage.setItem("cle", "une valeur");   // écrire
const v = localStorage.getItem("cle");        // lire
localStorage.removeItem("cle");               // effacer
AvertissementlocalStorage ne stocke que du TEXTE

D’où l’obligation de passer par JSON :

// Sauvegarder un tableau
localStorage.setItem("taches", JSON.stringify(taches));

// Le relire
const taches = JSON.parse(localStorage.getItem("taches"));

Si vous oubliez JSON.stringify, le navigateur enregistre bêtement "[object Object]" et vos données sont perdues.

ImportantCe qu’il ne faut JAMAIS mettre dans localStorage

Tout code JavaScript de la page peut le lire. On n’y met donc jamais :

  • de mot de passe
  • de jeton de connexion
  • de donnée personnelle sensible

C’est fait pour des préférences et des brouillons : thème choisi, liste de courses, panier en cours.

Étape 1 — La liste de tâches qui survit

Reprenez la liste de tâches de la séance 10, et ajoutez la persistance.

taches.js
const CLE = "mes-taches";

let taches = [];

function sauvegarder() {
  localStorage.setItem(CLE, JSON.stringify(taches));
}

function charger() {
  const texte = localStorage.getItem(CLE);

  if (texte === null) {
    taches = []; // première visite : rien de sauvegardé
    return;
  }

  taches = JSON.parse(texte);
}

function afficher() {
  liste.innerHTML = "";

  for (const tache of taches) {
    const li = document.createElement("li");
    li.textContent = tache;
    liste.appendChild(li);
  }

  compteur.textContent = `${taches.length} tâche(s)`;
}

ajoutForm.addEventListener("submit", (event) => {
  event.preventDefault();

  const texte = champ.value.trim();
  if (texte === "") {
    erreur.textContent = "La tâche ne peut pas être vide.";
    return;
  }

  erreur.textContent = "";
  taches.push(texte);
  champ.value = "";

  sauvegarder(); // ← la nouveauté
  afficher();
});

charger();
afficher();

Fermez complètement le navigateur, rouvrez la page : vos tâches sont encore là. 🎉

AstuceVoir le contenu du coffre

Dans l’inspecteur du navigateur, onglet Application (Chrome) ou Stockage (Firefox) → Local Storage. Vous y voyez vos clés et leurs valeurs, et vous pouvez les supprimer à la main pour tester.

4. Quand ça tourne mal : les erreurs

Ajoutez ceci dans la console du navigateur et rechargez la page :

localStorage.setItem("mes-taches", "ceci n'est pas du JSON");

La page plante. La console affiche du rouge, et plus rien ne fonctionne.

ImportantCe qui se passe quand une erreur survient

JavaScript arrête tout, immédiatement. Toutes les lignes qui suivaient ne sont pas exécutées — d’où la page figée.

On dit que le programme lève une exception.

try / catch

try {
  // le code qui pourrait mal tourner
  taches = JSON.parse(texte);
} catch (erreur) {
  // ce qu'on fait SI ça a mal tourné
  console.log("Données illisibles, on repart de zéro.");
  taches = [];
}
try {            ESSAIE ceci
  …
} catch (e) {    SI ÇA CASSE, fais plutôt ça
  …              (e contient la description du problème)
}
NoteL’image de l’airbag

try / catch ne répare pas le problème : il empêche l’accident d’être fatal. Le programme continue, avec un comportement de secours.

Sans lui, la moindre donnée corrompue rend toute votre page inutilisable.

Étape 2 — Une fonction charger qui résiste

taches.js
function charger() {
  const texte = localStorage.getItem(CLE);

  if (texte === null) {
    taches = [];
    return;
  }

  try {
    taches = JSON.parse(texte);
  } catch (erreur) {
    console.log("Sauvegarde corrompue :", erreur.message);
    taches = [];
    localStorage.removeItem(CLE); // on nettoie
  }
}

Refaites le test : mettez n’importe quoi dans localStorage, rechargez. La page fonctionne quand même, en repartant d’une liste vide.

AvertissementN’entourez pas tout votre code d’un try
try {
  // 200 lignes
} catch (e) {
  console.log("erreur");  // ❌ on ne sait plus ce qui a cassé
}

try doit encadrer le minimum : l’instruction précise qui peut échouer. Sinon vous masquez vos propres bugs et le débogage devient impossible.

5. Déclencher soi-même une erreur

On peut refuser une situation avec throw :

function racine(n) {
  if (n < 0) {
    throw new Error("Impossible : nombre négatif");
  }
  return Math.sqrt(n);
}

Et l’attraper à l’utilisation :

try {
  console.log(racine(-4));
} catch (erreur) {
  console.log(erreur.message); // Impossible : nombre négatif
}
Astucethrow ou return false : lequel choisir ?
  • return false (ou null) quand l’échec est normal et attendu : un retrait refusé faute de solde, un formulaire mal rempli.
  • throw quand la situation est anormale et ne devrait jamais arriver : un fichier illisible, une donnée corrompue, un paramètre absurde.

Dans les deux cas, on ne laisse jamais le programme continuer avec une valeur fausse. C’est ça, la fiabilité.

Exercice 12.2 — Prédire

Qu’affiche ce programme, et dans quel ordre ?

console.log("A");

try {
  console.log("B");
  JSON.parse("{pas du json}");
  console.log("C");
} catch (e) {
  console.log("D");
}

console.log("E");
A
B
D
E

C ne s’affiche jamais. Dès que JSON.parse échoue, JavaScript saute directement au bloc catch : tout ce qui restait dans le try est abandonné.

Et E s’affiche bien : grâce au catch, le programme reprend son cours normal après le bloc. Sans try/catch, ni D ni E n’auraient été affichés.

6. À toi de jouer

Exercice 12.3 — Repérer le JSON invalide

Lesquels de ces textes JSON.parse accepte-t-il ?

a)  {"nom": "Camille", "age": 19}
b)  {'nom': 'Camille'}
c)  {nom: "Camille"}
d)  {"nom": "Camille",}
e)  [1, 2, 3]
f)  "juste du texte"
Valide ? Pourquoi
a) ✅ Parfait
b) ❌ Guillemets simples : JSON exige des doubles
c) ❌ Le nom de propriété doit être entre guillemets
d) ❌ Virgule en trop avant }
e) ✅ Un tableau est du JSON valide
f) ✅ Une chaîne seule aussi (avec ses guillemets)

À retenir : JSON ne contient pas forcément un objet. Un tableau, un nombre, une chaîne sont du JSON valide.

Exercice 12.4 — Une fonction de lecture sûre

Écrivez lireJSON(texte, valeurParDefaut) qui renvoie l’objet correspondant au texte, ou valeurParDefaut si le texte n’est pas du JSON valide.

console.log(lireJSON('{"a":1}', {}));    // { a: 1 }
console.log(lireJSON("n'importe quoi", {})); // {}
console.log(lireJSON(null, []));         // []
app.js
function lireJSON(texte, valeurParDefaut) {
  if (texte === null || texte === "") {
    return valeurParDefaut;
  }

  try {
    return JSON.parse(texte);
  } catch (erreur) {
    console.log("JSON invalide :", erreur.message);
    return valeurParDefaut;
  }
}

Deux protections successives :

  1. Le if gère le cas « rien à lire » — ce n’est pas une erreur, c’est normal (première visite).
  2. Le try/catch gère le cas « il y a quelque chose, mais c’est cassé ».

On peut mettre un return à l’intérieur d’un try : la fonction rend la valeur et sort normalement.

Exercice 12.5 — Le carnet de notes qui se souvient 💾

Reprenez le répertoire de contacts de la séance 11 et rendez-le persistant.

Contraintes :

  1. Après chaque ajout, sauvegarder dans localStorage
  2. Au chargement, restaurer la liste — en reconstruisant de vrais objets Contact, pas de simples objets plats
  3. Toute la lecture protégée par try/catch
  4. Un bouton Tout effacer qui vide la liste et le stockage
AvertissementLe piège de cet exercice

JSON.parse renvoie des objets ordinaires. Il ne sait rien de vos classes :

const c = JSON.parse('{"nom":"Camille","email":"c@x.fr"}');
c.resume(); // ❌ ERREUR : resume is not a function

Les données sont là, mais les méthodes ont disparu au passage par le texte. Il faut donc recréer les objets avec new.

contacts.js
class Contact {
  constructor(nom, email, telephone) {
    this.nom = nom;
    this.email = email;
    this.telephone = telephone;
  }

  resume() {
    return `${this.nom} — ${this.email}`;
  }
}

const CLE = "mes-contacts";
let contacts = [];

function sauvegarder() {
  localStorage.setItem(CLE, JSON.stringify(contacts));
}

function charger() {
  const texte = localStorage.getItem(CLE);

  if (texte === null) {
    contacts = [];
    return;
  }

  try {
    const bruts = JSON.parse(texte);
    contacts = [];

    // on RECONSTRUIT de vrais Contact, pour retrouver les méthodes
    for (const brut of bruts) {
      contacts.push(new Contact(brut.nom, brut.email, brut.telephone));
    }
  } catch (erreur) {
    console.log("Sauvegarde illisible :", erreur.message);
    contacts = [];
    localStorage.removeItem(CLE);
  }
}

function afficher() {
  liste.innerHTML = "";

  for (const contact of contacts) {
    const li = document.createElement("li");
    li.textContent = contact.resume(); // ← ne marche que si c'est un vrai Contact
    liste.appendChild(li);
  }

  compteur.textContent = `${contacts.length} contact(s) enregistré(s)`;
}

formulaire.addEventListener("submit", (event) => {
  event.preventDefault();

  const nom = nomEl.value.trim();
  const email = emailEl.value.trim();

  if (nom === "") {
    erreur.textContent = "Le nom est obligatoire.";
    return;
  }
  if (!email.includes("@")) {
    erreur.textContent = "L'email doit contenir un @.";
    return;
  }

  erreur.textContent = "";
  contacts.push(new Contact(nom, email, telEl.value.trim()));
  formulaire.reset();

  sauvegarder();
  afficher();
});

document.querySelector("#effacer").addEventListener("click", () => {
  contacts = [];
  localStorage.removeItem(CLE);
  afficher();
});

charger();
afficher();

La boucle de reconstruction est le cœur de l’exercice. Elle illustre une vérité générale : le JSON transporte des données, jamais du comportement.

7. Fiche mémo

Les deux conversions

const texte = JSON.stringify(objet); // objet → texte
const objet = JSON.parse(texte);     // texte → objet

Sauvegarder

localStorage.setItem("cle", JSON.stringify(donnees));
const donnees = JSON.parse(localStorage.getItem("cle"));
localStorage.removeItem("cle");

getItem renvoie null si la clé n’existe pas → toujours tester.

Se protéger

try {
  donnees = JSON.parse(texte);
} catch (erreur) {
  console.log(erreur.message);
  donnees = [];
}

Refuser une situation

if (n < 0) {
  throw new Error("message clair");
}

Les 3 pièges

  1. localStorage ne stocke que du texte → JSON.stringify obligatoire
  2. JSON exige des guillemets doubles et pas de virgule finale
  3. JSON.parse rend des objets sans méthodes → reconstruire avec new

8. Avant la prochaine séance