localStorage : JSON et structure des données
localStorage ne conserve que des chaînes. Pour des objets, des tableaux, des nombres, des booléens ou des dates, on les sérialise en JSON.
Objets et tableaux
Section intitulée « Objets et tableaux »const preferences = { theme: 'dark', langue: 'fr', taillePolice: 16 };localStorage.setItem('preferences', JSON.stringify(preferences));
const lues = JSON.parse(localStorage.getItem('preferences') ?? 'null');console.log(lues?.theme); // 'dark'Sans JSON.stringify, un objet est stocké sous la forme "[object Object]" : toute la donnée est perdue.
JSON.parse lève une exception si le texte est invalide (donnée corrompue ou modifiée à la main) : l’encadrer d’un try/catch.
Un module de lecture et d’écriture
Section intitulée « Un module de lecture et d’écriture »const PREFIXE = 'monapp:';
export function lire(cle, defaut = null) { try { const brut = localStorage.getItem(PREFIXE + cle); return brut === null ? defaut : JSON.parse(brut); } catch { return defaut; }}
export function ecrire(cle, valeur) { try { localStorage.setItem(PREFIXE + cle, JSON.stringify(valeur)); return true; } catch { return false; }}
export function supprimer(cle) { try { localStorage.removeItem(PREFIXE + cle); } catch { /* ignoré */ }}Le préfixe (monapp:) évite les collisions avec d’autres scripts de la même origine (bibliothèques, autres applications du même domaine) et permet de supprimer uniquement les clés de l’application :
export function toutEffacer() { const aSupprimer = []; for (let i = 0; i < localStorage.length; i++) { const cle = localStorage.key(i); if (cle?.startsWith(PREFIXE)) aSupprimer.push(cle); } aSupprimer.forEach((cle) => localStorage.removeItem(cle));}Collecter les clés d’abord, puis les supprimer : modifier le stockage pendant la boucle décale les index.
Typage avec TypeScript
Section intitulée « Typage avec TypeScript »type Preferences = { theme: 'light' | 'dark'; langue: string };
const PAR_DEFAUT: Preferences = { theme: 'light', langue: 'fr' };
export function lirePreferences(): Preferences { const lues = lire<Partial<Preferences>>('preferences', {}); return { ...PAR_DEFAUT, ...lues };}JSON.parse renvoie une donnée non vérifiée : fusionner avec des valeurs par défaut, ou valider avec une bibliothèque de schémas (Zod) avant de s’en servir.
Types qui demandent attention
Section intitulée « Types qui demandent attention »| Type | Problème | Solution |
|---|---|---|
| Booléen | Revient en "true" / "false" |
JSON (JSON.parse('false') vaut false) |
| Nombre | Revient en chaîne | Number(...) ou JSON |
Date |
JSON la transforme en chaîne ISO | new Date(chaine) à la lecture |
undefined, fonctions |
Perdus par JSON | Ne pas les stocker |
Map, Set |
Perdus par JSON | [...ensemble], Object.fromEntries(map) |
Grands entiers (BigInt) |
Non pris en charge par JSON | Les stocker en chaîne |
| Références circulaires | JSON.stringify échoue |
Aplatir la donnée |
const rendezVous = { titre: 'Réunion', le: new Date().toISOString() };ecrire('rdv', rendezVous);const lu = lire('rdv');const quand = new Date(lu.le);Structurer les clés
Section intitulée « Structurer les clés »| Approche | Exemple | Quand |
|---|---|---|
| Une clé par réglage | monapp:theme, monapp:langue |
Peu de valeurs indépendantes |
| Une clé pour un objet | monapp:preferences |
Réglages lus et écrits ensemble |
| Une clé par élément | monapp:note:42 |
Peu d’éléments (pas de liste interrogeable) |
Éviter de stocker de longues listes dans une seule clé : chaque modification réécrit toute la chaîne. Au-delà de quelques centaines d’éléments ou d’une centaine de Ko, utiliser IndexedDB.
Versionner le format
Section intitulée « Versionner le format »Le code évolue, les données stockées restent. Prévoir un numéro de version :
const VERSION = 2;
export function chargerPreferences() { const brut = lire('preferences', null); if (!brut) return { version: VERSION, theme: 'light', langue: 'fr' }; return migrer(brut);}
function migrer(donnees) { let d = donnees; if (!d.version || d.version < 2) { // version 1 : { sombre: boolean } -> version 2 : { theme: 'light' | 'dark' } d = { version: 2, theme: d.sombre ? 'dark' : 'light', langue: d.langue ?? 'fr', }; } return d;}Écrire la fonction de migration avant de changer le format : un utilisateur qui revient après plusieurs versions doit pouvoir passer de la version 1 à la version courante.
Expiration
Section intitulée « Expiration »localStorage n’expire jamais. Pour une durée de validité :
export function ecrireAvecExpiration(cle, valeur, dureeMs) { ecrire(cle, { valeur, expireLe: Date.now() + dureeMs });}
export function lireAvecExpiration(cle, defaut = null) { const entree = lire(cle, null); if (!entree || Date.now() > entree.expireLe) { supprimer(cle); return defaut; } return entree.valeur;}Un cache peut ainsi être valable une heure (3_600_000 ms). L’expiration est vérifiée à la lecture : les entrées jamais relues restent stockées.
Taille utilisée
Section intitulée « Taille utilisée »function tailleUtilisee() { let octets = 0; for (let i = 0; i < localStorage.length; i++) { const cle = localStorage.key(i); octets += (cle.length + (localStorage.getItem(cle) ?? '').length) * 2; // UTF-16 : 2 octets par caractère } return octets;}console.log((tailleUtilisee() / 1024).toFixed(1), 'Ko');Les navigateurs comptent en général deux octets par caractère : 5 Mo représentent environ 2,5 millions de caractères.
Dépannage
Section intitulée « Dépannage »| Symptôme | Piste |
|---|---|
La valeur lue est "[object Object]" |
JSON.stringify oublié à l’écriture |
SyntaxError: Unexpected token … in JSON |
Valeur non JSON (ancien format, texte brut) : try/catch et migration |
| Une date devient une chaîne | JSON : reconstruire avec new Date(...) |
| Les anciennes données cassent la nouvelle version | Numéro de version et migration |
QuotaExceededError en écrivant une grosse liste |
Passer à IndexedDB |
| Une boucle de suppression saute des clés | Collecter les clés avant de supprimer |
Ubuntu 24.04 et 26.04 : différences à connaître
Section intitulée « Ubuntu 24.04 et 26.04 : différences à connaître »Aucune.
Pour aller plus loin
Section intitulée « Pour aller plus loin »- JSON
- Suite : Synchronisation entre onglets