JSON en JavaScript : Guide Complet de parse, stringify et Transformations Avancees

Maitrisez JSON.parse et JSON.stringify en JavaScript : fonctions reviver et replacer, gestion des dates, objets cycliques et serialisation personnalisee.

Mahmoud DEVO
Mahmoud DEVO
December 27, 2025 11 min read
JSON en JavaScript : Guide Complet de parse, stringify et Transformations Avancees

JSON en JavaScript : Guide Complet de parse, stringify et Transformations Avancees

Introduction

JSON (JavaScript Object Notation) est le format d’echange de donnees le plus utilise dans le developpement web moderne. Leger, lisible par les humains et facilement analysable par les machines, JSON est devenu le standard de facto pour la communication entre clients et serveurs.

Que vous construisiez une API REST, stockiez des configurations, ou communiquiez avec des services tiers, vous manipulez quotidiennement du JSON. JavaScript offre deux methodes natives puissantes pour travailler avec ce format : JSON.parse() et JSON.stringify().

Pourquoi JSON est-il si populaire ?

JSON presente plusieurs avantages qui expliquent son adoption massive :

  • Simplicite : La syntaxe est intuitive et proche des objets JavaScript
  • Interoperabilite : Supporte par tous les langages de programmation modernes
  • Legerete : Moins verbeux que XML, reduisant la bande passante
  • Typage natif : Supporte les types primitifs (string, number, boolean, null, array, object)

Cas d’usage courants

// Communication avec une API REST
const response = await fetch('/api/users');
const users = await response.json(); // Utilise JSON.parse() en interne

// Stockage local dans le navigateur
localStorage.setItem('preferences', JSON.stringify(userPrefs));
const savedPrefs = JSON.parse(localStorage.getItem('preferences'));

// Configuration d'applications
const config = JSON.parse(fs.readFileSync('config.json', 'utf8'));

Dans cet article complet, nous allons explorer en profondeur les methodes JSON.parse() et JSON.stringify(), leurs fonctions avancees (reviver et replacer), et les techniques pour gerer les cas complexes comme les dates, les objets cycliques et les classes personnalisees.


JSON.parse() : Convertir une Chaine en Objet

La methode JSON.parse() analyse une chaine de caracteres JSON et construit la valeur JavaScript ou l’objet correspondant.

Syntaxe de base

JSON.parse(text)
JSON.parse(text, reviver)

Parametres :

  • text : La chaine JSON a analyser
  • reviver (optionnel) : Une fonction de transformation appelee pour chaque paire cle/valeur

Exemples simples

// Parsing d'un objet simple
const jsonString = '{"name": "Alice", "age": 30, "active": true}';
const user = JSON.parse(jsonString);
console.log(user.name); // "Alice"
console.log(typeof user.age); // "number"

// Parsing d'un tableau
const arrayJson = '[1, 2, 3, "quatre", null]';
const arr = JSON.parse(arrayJson);
console.log(arr.length); // 5

// Parsing de valeurs primitives
JSON.parse('42');        // 42
JSON.parse('"hello"');   // "hello"
JSON.parse('true');      // true
JSON.parse('null');      // null

La fonction reviver : Transformation au parsing

La fonction reviver est un outil puissant qui permet de transformer les valeurs pendant le parsing. Elle est appelee pour chaque paire cle/valeur, de la plus profonde a la racine.

Signature de la fonction reviver

function reviver(key, value) {
  // key : la cle de la propriete (chaine vide pour la racine)
  // value : la valeur associee
  // this : l'objet contenant la propriete
  return value; // La valeur transformee
}

Exemple : Mise en majuscules des noms

const jsonString = '[{"name":"John","score":51},{"name":"Jack","score":17}]';

const data = JSON.parse(jsonString, function reviver(key, value) {
  if (key === 'name') {
    return value.toUpperCase();
  }
  return value;
});

console.log(data);
// [{ name: "JOHN", score: 51 }, { name: "JACK", score: 17 }]

Exemple : Conversion automatique des dates

Un probleme frequent avec JSON est la perte du type Date. Les dates sont serialisees en chaines ISO 8601 :

const jsonWithDate = '{"created": "2025-01-15T10:30:00.000Z", "name": "Event"}';

// Sans reviver : created est une chaine
const withoutReviver = JSON.parse(jsonWithDate);
console.log(typeof withoutReviver.created); // "string"

// Avec reviver : conversion automatique en Date
const isoDatePattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/;

const withReviver = JSON.parse(jsonWithDate, (key, value) => {
  if (typeof value === 'string' && isoDatePattern.test(value)) {
    return new Date(value);
  }
  return value;
});

console.log(withReviver.created instanceof Date); // true
console.log(withReviver.created.getFullYear());   // 2025

Exemple : Reconstruction de classes personnalisees

class User {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }

  greet() {
    return `Bonjour, je suis ${this.name}`;
  }

  static fromJSON(json) {
    return new User(json.name, json.email);
  }
}

const jsonString = '{"$type": "User", "name": "Alice", "email": "alice@example.com"}';

const reviver = (key, value) => {
  if (value && value.$type === 'User') {
    return User.fromJSON(value);
  }
  return value;
};

const user = JSON.parse(jsonString, reviver);
console.log(user.greet()); // "Bonjour, je suis Alice"
console.log(user instanceof User); // true

Gestion des erreurs de parsing

JSON.parse() lance une SyntaxError si la chaine JSON est invalide. Il est essentiel de toujours entourer les appels avec un try/catch :

function safeJsonParse(jsonString, defaultValue = null) {
  try {
    return JSON.parse(jsonString);
  } catch (error) {
    console.error('Erreur de parsing JSON:', error.message);
    return defaultValue;
  }
}

// Utilisation
const validJson = '{"name": "Test"}';
const invalidJson = '{name: "Test"}'; // Guillemets manquants

console.log(safeJsonParse(validJson));      // { name: "Test" }
console.log(safeJsonParse(invalidJson, {})); // {} (valeur par defaut)

Validation avant parsing

Pour une validation plus robuste, vous pouvez verifier la structure apres parsing :

function parseAndValidate(jsonString, schema) {
  try {
    const parsed = JSON.parse(jsonString);

    // Validation basique du schema
    for (const [key, type] of Object.entries(schema)) {
      if (typeof parsed[key] !== type) {
        throw new Error(`Propriete "${key}" doit etre de type "${type}"`);
      }
    }

    return { success: true, data: parsed };
  } catch (error) {
    return { success: false, error: error.message };
  }
}

// Utilisation
const result = parseAndValidate(
  '{"name": "Alice", "age": 30}',
  { name: 'string', age: 'number' }
);

if (result.success) {
  console.log('Donnees valides:', result.data);
} else {
  console.error('Validation echouee:', result.error);
}

JSON.stringify() : Convertir un Objet en Chaine

La methode JSON.stringify() convertit une valeur JavaScript en chaine JSON.

Syntaxe complete

JSON.stringify(value)
JSON.stringify(value, replacer)
JSON.stringify(value, replacer, space)

Parametres :

  • value : La valeur a convertir
  • replacer (optionnel) : Fonction de transformation ou tableau de proprietes a inclure
  • space (optionnel) : Indentation pour le formatage (nombre ou chaine)

Exemples simples

// Objet simple
const user = { name: 'Bob', age: 25 };
console.log(JSON.stringify(user));
// '{"name":"Bob","age":25}'

// Tableau
const numbers = [1, 2, 3];
console.log(JSON.stringify(numbers));
// '[1,2,3]'

// Valeurs primitives
console.log(JSON.stringify('hello')); // '"hello"'
console.log(JSON.stringify(42));      // '42'
console.log(JSON.stringify(true));    // 'true'
console.log(JSON.stringify(null));    // 'null'

La fonction replacer : Filtrage et transformation

La fonction replacer permet de controler quelles proprietes sont incluses et comment elles sont transformees.

Signature de la fonction replacer

function replacer(key, value) {
  // key : la cle de la propriete
  // value : la valeur associee
  // this : l'objet contenant la propriete
  // Retourner undefined pour exclure la propriete
  return value;
}

Exemple : Filtrer des proprietes sensibles

const user = {
  name: 'Alice',
  email: 'alice@example.com',
  password: 'secret123',
  apiKey: 'sk-abc123',
  role: 'admin'
};

const safeReplacer = (key, value) => {
  // Exclure les proprietes sensibles
  const sensitiveKeys = ['password', 'apiKey', 'secret'];
  if (sensitiveKeys.includes(key)) {
    return undefined; // Exclut la propriete
  }
  return value;
};

console.log(JSON.stringify(user, safeReplacer, 2));
// {
//   "name": "Alice",
//   "email": "alice@example.com",
//   "role": "admin"
// }

Exemple : Transformer les valeurs

const userRecords = [
  { name: 'Joe', points: 14.9, level: 31.5 },
  { name: 'Jane', points: 22.3, level: 45.7 }
];

const anonymizedReport = JSON.stringify(userRecords, (key, value) => {
  if (key === 'name') {
    return undefined; // Supprimer les noms (anonymisation)
  }
  if (typeof value === 'number') {
    return Math.floor(value); // Arrondir les nombres
  }
  return value;
}, 2);

console.log(anonymizedReport);
// [
//   { "points": 14, "level": 31 },
//   { "points": 22, "level": 45 }
// ]

Utiliser un tableau comme replacer

Au lieu d’une fonction, vous pouvez passer un tableau de noms de proprietes a inclure :

const user = {
  id: 1,
  name: 'Alice',
  email: 'alice@example.com',
  password: 'secret',
  createdAt: '2025-01-15'
};

// Inclure uniquement certaines proprietes
const publicJson = JSON.stringify(user, ['id', 'name', 'email']);
console.log(publicJson);
// '{"id":1,"name":"Alice","email":"alice@example.com"}'

Le parametre space : Formatage lisible

Le troisieme parametre permet d’ajouter une indentation pour rendre le JSON lisible :

const data = {
  name: 'Project',
  version: '1.0.0',
  dependencies: {
    lodash: '^4.17.21',
    axios: '^1.6.0'
  }
};

// Indentation avec 2 espaces
console.log(JSON.stringify(data, null, 2));
// {
//   "name": "Project",
//   "version": "1.0.0",
//   "dependencies": {
//     "lodash": "^4.17.21",
//     "axios": "^1.6.0"
//   }
// }

// Indentation avec tabulation
console.log(JSON.stringify(data, null, '\t'));

// Indentation personnalisee
console.log(JSON.stringify(data, null, '----'));

La methode toJSON() : Serialisation personnalisee

Les objets peuvent definir une methode toJSON() pour controler leur serialisation :

class User {
  constructor(name, email, password) {
    this.name = name;
    this.email = email;
    this.password = password;
    this.createdAt = new Date();
  }

  // Methode appelee automatiquement par JSON.stringify()
  toJSON() {
    return {
      $type: 'User',
      name: this.name,
      email: this.email,
      // password est volontairement omis
      createdAt: this.createdAt.toISOString()
    };
  }
}

const user = new User('Alice', 'alice@example.com', 'secret123');
console.log(JSON.stringify(user, null, 2));
// {
//   "$type": "User",
//   "name": "Alice",
//   "email": "alice@example.com",
//   "createdAt": "2025-01-15T10:30:00.000Z"
// }

Cas d’Usage Avances

Conversion des dates : Le probleme et les solutions

Les objets Date sont automatiquement convertis en chaines ISO 8601 par JSON.stringify(), mais JSON.parse() ne les reconvertit pas automatiquement :

const event = {
  title: 'Conference',
  date: new Date('2025-06-15T14:00:00Z')
};

// Serialisation
const json = JSON.stringify(event);
console.log(json);
// '{"title":"Conference","date":"2025-06-15T14:00:00.000Z"}'

// Parsing sans reviver : date reste une chaine
const parsed = JSON.parse(json);
console.log(typeof parsed.date); // "string"
console.log(parsed.date.getMonth); // undefined

Solution : Reviver universel pour les dates

function dateReviver(key, value) {
  // Pattern ISO 8601 complet
  const isoPattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z?$/;

  if (typeof value === 'string' && isoPattern.test(value)) {
    const date = new Date(value);
    // Verifier que la date est valide
    if (!isNaN(date.getTime())) {
      return date;
    }
  }
  return value;
}

const parsed = JSON.parse(json, dateReviver);
console.log(parsed.date instanceof Date); // true
console.log(parsed.date.toLocaleDateString()); // "15/06/2025"

Deep Clone avec JSON : Avantages et limites

Une technique populaire pour cloner des objets est d’utiliser JSON :

// Clone profond simple
const original = {
  name: 'Config',
  settings: {
    theme: 'dark',
    notifications: true
  },
  items: [1, 2, { nested: 'value' }]
};

const clone = JSON.parse(JSON.stringify(original));

// Verification : modifications independantes
clone.settings.theme = 'light';
console.log(original.settings.theme); // "dark" (non affecte)

Limitations du clone JSON :

const problematic = {
  date: new Date(),           // Devient une chaine
  func: () => 'hello',        // Perdu (undefined)
  undef: undefined,           // Perdu
  symbol: Symbol('test'),     // Perdu
  infinity: Infinity,         // Devient null
  nan: NaN,                   // Devient null
  regex: /pattern/gi,         // Devient un objet vide {}
  map: new Map([['a', 1]]),   // Devient un objet vide {}
  set: new Set([1, 2, 3])     // Devient un objet vide {}
};

const cloned = JSON.parse(JSON.stringify(problematic));
console.log(cloned);
// {
//   date: "2025-01-15T...",  // Chaine au lieu de Date
//   infinity: null,
//   nan: null,
//   regex: {},
//   map: {},
//   set: {}
// }
// func, undef, symbol sont absents

Alternative moderne : structuredClone()

// Disponible dans les navigateurs modernes et Node.js 17+
const original = {
  date: new Date(),
  map: new Map([['key', 'value']]),
  set: new Set([1, 2, 3])
};

const clone = structuredClone(original);
console.log(clone.date instanceof Date); // true
console.log(clone.map instanceof Map);   // true

Objets cycliques : Le probleme et les solutions

JSON ne supporte pas les references circulaires :

const world = {
  name: 'World',
  regions: []
};

const europe = {
  name: 'Europe',
  parent: world // Reference circulaire !
};

world.regions.push(europe);

// ERREUR : TypeError: Converting circular structure to JSON
JSON.stringify(world);

Solution 1 : Detecter et remplacer les cycles

function stringifyWithCycles(obj) {
  const seen = new WeakSet();

  return JSON.stringify(obj, (key, value) => {
    if (typeof value === 'object' && value !== null) {
      if (seen.has(value)) {
        return '[Circular Reference]';
      }
      seen.add(value);
    }
    return value;
  }, 2);
}

console.log(stringifyWithCycles(world));
// {
//   "name": "World",
//   "regions": [
//     {
//       "name": "Europe",
//       "parent": "[Circular Reference]"
//     }
//   ]
// }

Solution 2 : Utiliser des identifiants de reference

function stringifyWithRefs(obj) {
  const refs = new Map();
  let refId = 0;

  // Premier passage : identifier les objets
  JSON.stringify(obj, (key, value) => {
    if (typeof value === 'object' && value !== null) {
      if (!refs.has(value)) {
        refs.set(value, `$ref:${refId++}`);
      }
    }
    return value;
  });

  // Second passage : remplacer les cycles
  const seen = new WeakSet();
  return JSON.stringify(obj, (key, value) => {
    if (typeof value === 'object' && value !== null) {
      if (seen.has(value)) {
        return { $ref: refs.get(value) };
      }
      seen.add(value);
    }
    return value;
  }, 2);
}

Classes personnalisees avec $type

Pour serialiser et deserialiser des instances de classes :

// Definition des classes
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toJSON() {
    return { $type: 'Point', x: this.x, y: this.y };
  }

  static fromJSON(data) {
    return new Point(data.x, data.y);
  }

  distance(other) {
    return Math.sqrt((this.x - other.x) ** 2 + (this.y - other.y) ** 2);
  }
}

class Rectangle {
  constructor(topLeft, bottomRight) {
    this.topLeft = topLeft;
    this.bottomRight = bottomRight;
  }

  toJSON() {
    return {
      $type: 'Rectangle',
      topLeft: this.topLeft,
      bottomRight: this.bottomRight
    };
  }

  static fromJSON(data, reviver) {
    return new Rectangle(
      reviver('topLeft', data.topLeft),
      reviver('bottomRight', data.bottomRight)
    );
  }

  get area() {
    const width = Math.abs(this.bottomRight.x - this.topLeft.x);
    const height = Math.abs(this.bottomRight.y - this.topLeft.y);
    return width * height;
  }
}

// Registre des classes
const classRegistry = {
  Point: Point,
  Rectangle: Rectangle
};

// Reviver universel
function classReviver(key, value) {
  if (value && value.$type && classRegistry[value.$type]) {
    const ClassRef = classRegistry[value.$type];
    return ClassRef.fromJSON(value, classReviver);
  }
  return value;
}

// Utilisation
const rect = new Rectangle(new Point(0, 0), new Point(10, 5));
const json = JSON.stringify(rect, null, 2);
console.log(json);

const restored = JSON.parse(json, classReviver);
console.log(restored instanceof Rectangle); // true
console.log(restored.topLeft instanceof Point); // true
console.log(restored.area); // 50

Bonnes Pratiques

1. Toujours utiliser try/catch avec JSON.parse()

// MAL
const data = JSON.parse(untrustedInput); // Peut lancer une exception

// BIEN
let data;
try {
  data = JSON.parse(untrustedInput);
} catch (e) {
  console.error('JSON invalide:', e.message);
  data = defaultValue;
}

2. Valider les donnees apres parsing

Ne faites jamais confiance aux donnees JSON provenant de sources externes :

const parsed = JSON.parse(apiResponse);

// Valider la structure attendue
if (!parsed || typeof parsed.id !== 'number' || typeof parsed.name !== 'string') {
  throw new Error('Format de reponse invalide');
}

3. Utiliser le parametre space en developpement uniquement

// En developpement : lisible
console.log(JSON.stringify(data, null, 2));

// En production : compact (economie de bande passante)
const payload = JSON.stringify(data);

4. Definir toJSON() pour les classes complexes

class ApiResponse {
  constructor(data, meta) {
    this.data = data;
    this.meta = meta;
    this._internalCache = {}; // Propriete privee
  }

  toJSON() {
    // Exclure les proprietes internes
    return {
      data: this.data,
      meta: this.meta
    };
  }
}

5. Creer des helpers reutilisables

const JSONUtils = {
  safeParse(json, defaultValue = null) {
    try {
      return JSON.parse(json);
    } catch {
      return defaultValue;
    }
  },

  prettyPrint(obj) {
    return JSON.stringify(obj, null, 2);
  },

  parseWithDates(json) {
    const isoPattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/;
    return JSON.parse(json, (key, value) => {
      if (typeof value === 'string' && isoPattern.test(value)) {
        return new Date(value);
      }
      return value;
    });
  }
};

6. Documenter les formats JSON de votre API

/**
 * Format de reponse utilisateur
 * @typedef {Object} UserResponse
 * @property {number} id - ID unique
 * @property {string} name - Nom complet
 * @property {string} email - Email
 * @property {string} createdAt - Date ISO 8601
 */

Pieges Courants

1. undefined est ignore silencieusement

const obj = {
  name: 'Test',
  value: undefined,
  items: [1, undefined, 3]
};

console.log(JSON.stringify(obj));
// '{"name":"Test","items":[1,null,3]}'
// value est absent, undefined dans le tableau devient null

2. Les fonctions sont perdues

const obj = {
  name: 'Calculator',
  add: (a, b) => a + b,
  multiply: function(a, b) { return a * b; }
};

console.log(JSON.stringify(obj));
// '{"name":"Calculator"}'
// Les fonctions sont completement ignorees

3. NaN et Infinity deviennent null

const numbers = {
  valid: 42,
  notANumber: NaN,
  infinite: Infinity,
  negInfinite: -Infinity
};

console.log(JSON.stringify(numbers));
// '{"valid":42,"notANumber":null,"infinite":null,"negInfinite":null}'

4. Les proprietes Symbol sont ignorees

const sym = Symbol('secret');
const obj = {
  name: 'Public',
  [sym]: 'Hidden value'
};

console.log(JSON.stringify(obj));
// '{"name":"Public"}'
// La propriete Symbol est absente

5. Les Map et Set deviennent des objets vides

const data = {
  map: new Map([['key', 'value']]),
  set: new Set([1, 2, 3])
};

console.log(JSON.stringify(data));
// '{"map":{},"set":{}}'

// Solution : convertir avant serialisation
const serializable = {
  map: Object.fromEntries(data.map),
  set: Array.from(data.set)
};
console.log(JSON.stringify(serializable));
// '{"map":{"key":"value"},"set":[1,2,3]}'

6. Les BigInt lancent une erreur

const obj = { bigNumber: 9007199254740993n };

// TypeError: Do not know how to serialize a BigInt
JSON.stringify(obj);

// Solution : definir toJSON sur BigInt.prototype (deconseille)
// ou utiliser un replacer
const json = JSON.stringify(obj, (key, value) => {
  if (typeof value === 'bigint') {
    return value.toString() + 'n';
  }
  return value;
});

Conclusion

Les methodes JSON.parse() et JSON.stringify() sont des outils fondamentaux en JavaScript moderne. Leur maitrise est essentielle pour tout developpeur travaillant avec des APIs, du stockage local, ou des configurations.

Tableau Recapitulatif

MethodeUsageOptions Cles
JSON.parse(text)Convertir JSON en objet-
JSON.parse(text, reviver)Parsing avec transformationreviver(key, value)
JSON.stringify(value)Convertir objet en JSON-
JSON.stringify(value, replacer)Serialisation avec filtragereplacer(key, value) ou ['prop1', 'prop2']
JSON.stringify(value, null, space)JSON formatespace : nombre ou chaine

Points Cles a Retenir

  1. Toujours gerer les erreurs avec JSON.parse() dans un bloc try/catch
  2. Les dates necessitent un reviver pour etre reconverties en objets Date
  3. Le replacer filtre les proprietes sensibles (mots de passe, cles API)
  4. Les objets cycliques necessitent un traitement special
  5. Definir toJSON() sur vos classes pour controler leur serialisation
  6. Attention aux valeurs non supportees : undefined, fonctions, Symbol, NaN, Infinity, Map, Set, BigInt

En appliquant ces techniques et bonnes pratiques, vous pourrez manipuler les donnees JSON de maniere robuste et efficace dans tous vos projets JavaScript.

Advertisement

In-Article Ad

Dev Mode

Share this article

Mahmoud DEVO

Mahmoud DEVO

Senior Full-Stack Developer

I'm a passionate full-stack developer with 10+ years of experience building scalable web applications. I write about Vue.js, Node.js, PostgreSQL, and modern DevOps practices.

Enjoyed this article?

Subscribe to get more tech content delivered to your inbox.

Related Articles