Aller au contenu
Toova
Tous les outils

7 Astuces JSON Qui Vous Feront Gagner des Heures

Toova

Chaque développeur JavaScript utilise JSON.parse et JSON.stringify des dizaines de fois par semaine. Mais la plupart s'arrêtent aux bases — analyser des réponses API et sérialiser des objets en chaînes. L'API a plus à offrir, et certaines fonctionnalités moins utilisées résolvent des problèmes qui nécessiteraient autrement des bibliothèques tierces ou des heures de débogage.

Ce guide couvre sept astuces qui vont au-delà des comportements par défaut. Chacune est immédiatement applicable à de vraies bases de code. Pas d'abstractions, pas d'exemples inventés — ce sont des patterns qui apparaissent dans des systèmes en production.

Vous pouvez vérifier et explorer n'importe lequel des exemples de code de cet article en utilisant le Formateur JSON Toova, qui valide et affiche le JSON de manière lisible entièrement dans votre navigateur.

1. Clone profond avec le round-trip JSON (et ses limites)

La plus ancienne astuce du livre JavaScript : utiliser JSON.parse(JSON.stringify(obj)) pour créer un clone profond d'un objet simple.

const original = { a: 1, b: { c: 2 } };

// Approche naïve — les objets imbriqués en profondeur sont toujours partagés
const shallowCopy = { ...original }; // b pointe toujours vers le même objet

// Clone profond JSON — crée une copie complètement indépendante
const deepClone = JSON.parse(JSON.stringify(original));

deepClone.b.c = 99;
console.log(original.b.c); // 2 — original non modifié

Cela fonctionne parce que la sérialisation en chaîne et le parsing retour créent un arbre d'objets entièrement nouveau sans références partagées. C'est rapide, ne nécessite pas de dépendances et est disponible depuis ES5.

L'alternative moderne pour une utilisation en mémoire est structuredClone() :

// Alternative moderne : structuredClone() — gère plus de types
// Pris en charge dans Node.js 17+ et tous les navigateurs modernes
const clone = structuredClone(original);

Connaissez les limites de l'approche JSON avant de vous y fier :

// JSON.parse/stringify NE PEUT PAS gérer :
const broken = {
  date: new Date(),      // devient une chaîne — perd le prototype Date
  fn: () => 'hello',    // supprimé silencieusement
  undef: undefined,     // supprimé silencieusement
  inf: Infinity,        // devient null
  map: new Map(),       // devient {}
  cycle: null,          // les références circulaires lèvent une erreur
};

Si votre objet ne contient que des données simples — chaînes, nombres, booléens, tableaux et objets simples imbriqués — le round-trip JSON est sûr et rapide. Pour tout le reste, préférez structuredClone() ou une bibliothèque dédiée.

2. Replacer personnalisé pour le filtrage et le masquage

La plupart des développeurs savent que JSON.stringify prend un deuxième argument, mais l'utilisent rarement. Ce deuxième argument est le replacer : soit un tableau de clés à inclure, soit une fonction qui contrôle exactement comment chaque valeur est sérialisée.

Replacer tableau — liste blanche de clés spécifiques :

const user = {
  id: 'u_001',
  name: 'Alice',
  password: 'hunter2',        // ne doit pas apparaître dans les journaux
  creditCard: '4111111111111111', // ne doit pas apparaître dans les journaux
  role: 'admin',
};

// Replacer tableau : inclure uniquement ces clés
JSON.stringify(user, ['id', 'name', 'role']);
// '{"id":"u_001","name":"Alice","role":"admin"}'

Replacer fonction — transformer ou censurer des valeurs :

// Replacer fonction : contrôle total sur la clé/valeur
const masked = JSON.stringify(user, (key, value) => {
  if (key === 'password' || key === 'creditCard') return '[REDACTED]';
  if (key === '' ) return value; // objet racine — toujours retourner
  return value;
});
// '{"id":"u_001","name":"Alice","password":"[REDACTED]","creditCard":"[REDACTED]","role":"admin"}'

Une version plus sophistiquée masque les valeurs en fonction de leur forme plutôt que de leur nom de clé :

// Replacer pour le masquage basé sur le type
const sanitize = (key, value) => {
  if (typeof value === 'string' && value.match(/^4[0-9]{15}$/)) {
    return '****-****-****-' + value.slice(-4);
  }
  return value;
};

Cette technique est indispensable pour les middlewares de journalisation : vous voulez des journaux structurés avec un contexte d'objet complet, mais certains champs ne doivent jamais atteindre un agrégateur de journaux. Le replacer vous permet de gérer cela à la frontière de sérialisation plutôt que de disperser la logique de masquage dans toute la base de code.

3. Trier les clés de manière déterministe

L'ordre des clés d'objet en JavaScript suit l'ordre d'insertion (pour les clés chaînes). Deux objets avec les mêmes clés mais créés dans des ordres différents produisent des chaînes JSON différentes, ce qui casse les vérifications d'égalité naïves, les clés de cache et les hachages de contenu.

function sortedStringify(obj) {
  return JSON.stringify(obj, Object.keys(obj).sort());
}

const a = { z: 1, a: 2, m: 3 };
const b = { a: 2, m: 3, z: 1 };

sortedStringify(a) === sortedStringify(b); // true — ordre des clés normalisé

Pour les objets profondément imbriqués, appliquez le tri récursivement :

// Tri récursif des clés pour les objets imbriqués
function sortKeys(value) {
  if (Array.isArray(value)) return value.map(sortKeys);
  if (value !== null && typeof value === 'object') {
    return Object.fromEntries(
      Object.keys(value).sort().map((k) => [k, sortKeys(value[k])])
    );
  }
  return value;
}

const sorted = JSON.stringify(sortKeys(deepNested));

Le JSON trié est essentiel lorsque vous :

  • Générez des clés de cache à partir de corps de requête
  • Calculez des sommes de contrôle ou des signatures sur des charges utiles JSON
  • Comparez des réponses API dans des tests indépendamment de l'ordre des champs
  • Stockez des objets de configuration où l'ordre ne doit pas affecter l'égalité

Après le tri et le pretty-printing, utilisez l'outil Diff de Texte pour comparer deux chaînes JSON normalisées et voir exactement quelles valeurs ont changé entre les versions.

4. Gestion de BigInt sans perte de précision

Le type Number de JavaScript peut représenter en toute sécurité des entiers jusqu'à 253 − 1. Pour les ID générés par des systèmes distribués, les montants financiers en unités mineures ou les horodatages en nanosecondes, ce n'est pas suffisant. BigInt couvre les entiers de précision arbitraire, mais JSON.stringify ne sait pas quoi en faire.

const data = {
  amount: 9007199254740993n, // plus grand que Number.MAX_SAFE_INTEGER
};

// Ceci lève : TypeError: Do not know how to serialize a BigInt
JSON.stringify(data); // ERREUR

Les solutions standard :

// Solution 1 : Convertir en chaîne avec un replacer
JSON.stringify(data, (key, value) =>
  typeof value === 'bigint' ? value.toString() : value
);
// '{"amount":"9007199254740993"}'

// Solution 2 : toJSON() sur le prototype BigInt (monkey-patch — à utiliser avec précaution)
BigInt.prototype.toJSON = function () { return this.toString(); };
JSON.stringify(data); // '{"amount":"9007199254740993"}'

Côté parsing, une fonction reviver peut restaurer les valeurs BigInt depuis leur représentation en chaîne :

// Reviver pour restaurer BigInt lors du parsing
const revived = JSON.parse('{"amount":"9007199254740993"}', (key, value) => {
  if (key === 'amount') return BigInt(value);
  return value;
});
console.log(typeof revived.amount); // 'bigint'

Gardez la conversion BigInt-en-chaîne dans une couche de sérialisation partagée pour qu'elle soit appliquée de manière cohérente. Laisser les BigInts s'échapper vers des appels JSON.stringify ad hoc aux extrémités d'une base de code entraîne des erreurs imprévisibles difficiles à tracer.

5. Détection des références circulaires

Une référence circulaire se produit lorsqu'un objet contient une référence à lui-même ou à un ancêtre dans le graphe d'objets. Elles sont plus courantes qu'on ne le pense : les émetteurs d'événements, les nœuds DOM, les nœuds fiber de React et les entités ORM ont fréquemment des back-références.

// Exemple de référence circulaire
const obj = { name: 'node' };
obj.self = obj; // obj se référence lui-même

JSON.stringify(obj); // lève : TypeError: Converting circular structure to JSON

Gérez-la avec un replacer personnalisé qui suit les objets visités :

// Gestionnaire manuel de référence circulaire
function safeStringify(obj) {
  const seen = new WeakSet();
  return JSON.stringify(obj, (key, value) => {
    if (typeof value === 'object' && value !== null) {
      if (seen.has(value)) return '[Circular]';
      seen.add(value);
    }
    return value;
  });
}

safeStringify(obj); // '{"name":"node","self":"[Circular]"}'

Le WeakSet est la bonne structure de données ici : il contient des références d'objets sans empêcher le garbage collection, et la recherche est O(1). Ce pattern fonctionne également dans les middlewares de journalisation où vous voulez que les erreurs se dégradent gracieusement plutôt que de lever une exception pendant la sérialisation.

Une variation courante : au lieu de marquer les références circulaires comme [Circular], remplacez-les par une chaîne de chemin comme [Circular: $.config.parent] pour rendre l'emplacement de la référence explicite lors du débogage.

6. Pretty-print des tableaux sur une seule ligne

Le troisième argument de JSON.stringify est l'indentation. Passer 2 ou 4 développe tout sur plusieurs lignes, ce qui est excellent pour les objets mais peut être verbeux pour les tableaux de primitives comme les tags, les ID ou les coordonnées.

const mixed = {
  title: 'Report',
  tags: ['json', 'api', 'debug'],
  config: { indent: 2, sortKeys: true },
  count: 42,
};

// Par défaut : tout sur plusieurs lignes
JSON.stringify(mixed, null, 2);
// {
//   "title": "Report",
//   "tags": [
//     "json",
//     "api",
//     "debug"
//   ],
//   "config": {
//     "indent": 2,
//     "sortKeys": true
//   },
//   "count": 42
// }

Vous pouvez post-traiter la sortie pour réduire les tableaux simples sur une seule ligne :

// Replacer personnalisé pour les tableaux sur une seule ligne
function prettyMixed(obj) {
  const raw = JSON.stringify(obj, null, 2);
  // Réduire les tableaux qui ne contiennent que des primitives sur une seule ligne
  return raw.replace(
    /\[\n\s+([\s\S]*?)\n\s+\]/g,
    (match, inner) => {
      const items = inner.split(',\n').map((s) => s.trim());
      if (items.every((s) => !/^[{\[]/.test(s))) {
        return '[' + items.join(', ') + ']';
      }
      return match;
    }
  );
}

Le résultat : les objets restent sur plusieurs lignes pour la lisibilité, les tableaux de primitives se réduisent à une ligne pour la compacité. C'est le format utilisé dans de nombreux fichiers de configuration et sorties de journaux structurés où les humains et les machines doivent lire les mêmes données. Le Formateur JSON et le convertisseur JSON vers YAML gèrent automatiquement cette réduction.

7. Streaming de grand JSON

Charger un grand fichier JSON entièrement en mémoire avant de le parser est la seule erreur de performance la plus courante dans les pipelines de traitement JSON. Une réponse JSON de 100 Mo alloue au moins 100 Mo de tas pour la chaîne brute, puis une deuxième allocation pour l'objet parsé. Pour les fichiers de plus de quelques mégaoctets, un parser en streaming traite les données de manière incrémentale.

Sur Node.js avec une bibliothèque de streaming :

// node --experimental-vm-modules (ou utiliser une bibliothèque JSON en streaming)
// Streaming natif avec la proposition JSON Source Map (Stage 3, 2026) :

// Pour l'instant, utiliser une bibliothèque comme @streamparser/json ou jsonstream2
import { createReadStream } from 'fs';
import parser from '@streamparser/json';

const jsonParser = new parser.JSONParser();
jsonParser.onValue = ({ value, key, parent }) => {
  if (key === 'id') {
    console.log('ID trouvé :', value);
  }
};

createReadStream('large.json').pipe(jsonParser);

Dans le navigateur en utilisant l'API Fetch Streams :

// Côté navigateur avec Fetch + décodeur de flux JSON :
const response = await fetch('/api/large-data');
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  buffer += decoder.decode(value, { stream: true });
  // Traiter les objets JSON complets depuis le buffer...
}

Le streaming est le plus utile dans les pipelines de traitement batch, les grands endpoints d'export et les scripts d'analyse de journaux. Pour les réponses API typiques de moins de 1 Mo, le JSON.parse standard est suffisamment rapide et bien plus simple. Le seuil où le streaming commence à être rentable en pratique est d'environ 5 à 10 Mo, selon la complexité de la structure parsée et la taille du tas de l'environnement cible.

Pour explorer la structure de grandes réponses JSON avant de construire un parser en streaming, collez un échantillon dans le Formateur JSON pour voir quels chemins contiennent les données dont vous avez besoin, puis convertissez en CSV ou en YAML pour une analyse plus approfondie.

En résumé

Ces sept techniques couvrent les coins les plus impactants de l'API JSON :

  • Clone profond via round-trip — rapide pour les données simples, connaissez les limites
  • Replacer — filtrer les clés et masquer les valeurs sensibles à la frontière de sérialisation
  • Tri des clés — sortie déterministe pour les clés de cache, les signatures et les assertions de test
  • Gestion de BigInt — sérialiser en chaîne, restaurer au parsing
  • Détection des références circulaires — replacer basé sur WeakSet pour une sérialisation sûre
  • Pretty-print avec tableaux compacts — sortie lisible sans excès d'espaces blancs
  • Streaming de grand JSON — parsing incrémental pour les grands fichiers et réponses API

Pour la documentation complète des arguments replacer et space de JSON.stringify, consultez la référence MDN JSON.stringify. Pour l'alternative de clone profond moderne, consultez la documentation de structuredClone(), qui couvre tous les types pris en charge et les cas limites.