ToolActToolAct
Qu'est-ce que le JSON ?

Qu'est-ce que le JSON ?

Vie Pratique3 août 202615 min de lecture

Tout commence par une requête réseau

Un vendredi après-midi, je regarde par-dessus l'épaule d'un collègue. Il a ouvert les DevTools du navigateur, onglet Réseau, et vient de relancer sa page de développement. Il clique sur la requête qui traîne — celle qui met trois secondes à répondre — et ouvre l'onglet Aperçu. Là, au milieu de l'écran, une vingtaine de lignes s'affichent, en retrait les unes sous les autres, entre accolades. On distingue des noms de champs, des valeurs entre guillemets, quelques crochets. « Je ne comprends rien à ce que renvoie le serveur », dit-il.

Je regarde un peu plus loin et je vois ce qui cloche : le champ s'appelle user_id, mais l'application attend userId. La réponse est pourtant du bon format. Le problème n'est pas la forme, c'est le fond. On est dans un cas classique : deux équipes, deux conventions, et un contrat silencieux que personne n'a écrit.

Cette scène, vous l'avez vécue des centaines de fois. Cette « vingtaine de lignes entre accolades », c'est du JSON. Et il est partout : dans les réponses d'API, les fichiers de configuration, les bases de données, les messages entre microservices. C'est sans doute le format de données le plus répandu de l'histoire du logiciel. Et pourtant, si vous demandez à dix développeurs ce que c'est exactement, vous aurez dix réponses qui se recoupent à moitié.

Ce texte est une tentative de mettre tout ça noir sur blanc : ce qu'est le JSON, d'où il vient, pourquoi il a conquis le monde, ses pièges, ses limites, et ce qu'on peut mettre à la place quand il ne suffit plus. Sans langue de bois, avec des exemples concrets.

Ce qu'est vraiment le JSON

JSON, c'est l'acronyme de JavaScript Object Notation. On le doit à Douglas Crockford, qui l'a formalisé autour de l'an 2001. L'idée de départ est simple : le JavaScript sait déjà écrire des objets en dur, dans cette « notation d'objet » — ces blocs { cle: valeur } — et cette notation est lisible, compacte, et facile à produire par programme. Crockford en a extrait un sous-ensemble, l'a décrit précisément, et a posé le tout sur json.org, un site monolithique en HTML d'époque qui n'a pas bougé depuis, avec une page par langue.

Le format a ensuite été normalisé par l'ECMA sous le nom ECMA-404, puis par l'IETF dans la RFC 8259, publiée en décembre 2017, qui remplace la RFC 7159 de 2013, elle-même héritière de la RFC 4627 de 2006. Deux choses à retenir de ces textes : ils sont courts (la RFC 8259 tient en une vingtaine de pages, l'essentiel étant la grammaire), et ils ne décrivent rien de plus que ce que vous voyez quand vous ouvrez un fichier .json dans un éditeur.

Notons au passage que le nom est un peu malheureux. « JavaScript Object Notation », oui, mais à part la filiation syntaxique — les objets JavaScript s'écrivent comme du JSON, et inversement — le format n'a plus rien à voir avec JavaScript. Un fichier JSON, c'est du texte, c'est tout. Il se lit dans n'importe quel langage, se génère dans n'importe quel langage, et n'a besoin d'aucune connaissance de JavaScript pour être compris. Le nom est un vestige historique, comme le « disque dur » qu'on appelle encore ainsi alors qu'il n'y a plus de plateau magnétique à l'intérieur.

Ce qui fait sa force, ce n'est ni la nouveauté ni la sophistication. C'est qu'il répond à une exigence banale : échanger des données structurées entre deux programmes qui ne se connaissent pas, de la façon la plus simple possible. Un format que les humains peuvent lire — on l'ouvre, on comprend — et que les machines peuvent produire et consommer sans effort.

Pourquoi il a battu XML

Il faut se replonger dans les années 2000 pour mesurer le chemin parcouru. À l'époque, XML est le format par défaut pour tout ce qui est censé transporter de la donnée. Les services web SOAP, dont le seul nom faisait déjà froid dans le dos, vous obligeaient à encapsuler le moindre appel dans une enveloppe XML avec en-têtes et namespaces. Les fichiers de configuration ressemblaient à des annuaires téléphoniques : des dizaines de lignes pour exprimer ce qu'une seule déclaration suffirait à dire. Et le débat qui divisait les équipes — faut-il mettre une valeur dans un attribut ou dans un élément ? — occupait des heures de réunion, pendant que XSLT, censé donner un sens à tout ça, faisait fuir ceux qui s'y étaient collés un week-end.

Il y avait un problème de proportionnalité. Pour envoyer « bonjour » de A à B, on déroulait deux cents octets de balises, avec déclaration XML, namespace, et pièces justificatives. La donnée se noyait dans son propre emballage.

Le JSON s'est imposé comme une évidence. Il est compact : pour la même information, il utilise souvent deux à trois fois moins d'octets, et moins encore quand le document XML est verbeux. Surtout, il correspond un à un aux structures de données que tous les langages ont déjà en mémoire. Un objet, c'est une table de hachage, un dict, une map, un dictionnaire — appelez ça comme vous voulez. Un tableau, c'est une liste, un vecteur, un slice. Une chaîne, un nombre, un booléen, le vide. Il n'y a rien à apprendre : si vous savez écrire un dictionnaire dans votre langage, vous savez lire du JSON.

Et puis, pas besoin de schéma pour commencer. XML poussait vers les DTD, les XSD, la validation, toute une machinerie à installer avant d'avoir échangé un seul octet utile. JSON se contente d'être là, de décrire la structure au fil de l'eau, et de vous laisser le soin de valider ce qui doit l'être. La courbe d'apprentissage tient dans la lecture d'une page.

Le plus honnête, c'est de dire que JSON n'a pas gagné parce qu'il était beau. Il a gagné parce que tout le reste était plus pénible. Quand un format demande moins de cérémonial que l'autre pour arriver au même résultat, et que tout l'écosystème de l'autre est parti dans les compétences pointues, le choix devient vite fait.

Les six types de données

JSON a exactement six types de données. Pas un de plus. C'est tout l'intérêt : le format est clos, défini, et n'a pas de cas particulier caché.

L'objet — une collection non ordonnée de paires clé→valeur, entre accolades :

{ "nom": "Camille", "age": 34 }

Les clés sont toujours des chaînes (entre guillemets doubles), les valeurs peuvent être n'importe quel type, y compris un autre objet.

Le tableau — une liste ordonnée de valeurs, entre crochets :

["lundi", "mardi", "mercredi"]

La chaîne — du texte entre guillemets doubles, avec un jeu d'échappements défini :

"une phrase avec des guillemets : \" et une barre : \\"

Le nombre — un entier ou un décimal, éventuellement en notation exponentielle. Pas de zéros en tête, pas de NaN, pas d'infini :

42
-3.14
1.6e-19

Le booléen — écrit en minuscules, true ou false, comme dans la plupart des langages (et pas True comme en Python ni TRUE comme en SQL) :

true

null — la valeur vide, en minuscules, pour représenter « rien » :

null

Rien d'autre. Pas de date, pas d'octets binaires, pas d'identifiant natif, pas de type décimal exact. Pour tout le reste, on bricole — et on en reparlera.

Voici maintenant un exemple réaliste, un enregistrement utilisateur tel qu'une API en renverrait :

{
  "id": "a3f9c2",
  "nom": "Camille Martin",
  "email": "camille.martin@exemple.fr",
  "actif": true,
  "role": {
    "code": "admin",
    "permissions": ["lecture", "ecriture", "suppression"]
  },
  "dateInscription": "2024-06-18T09:30:00Z",
  "derniereConnexion": null,
  "score": 87.5
}

Remarquez la combinaison des six types dans un même document : un objet imbriqué (role), un tableau (permissions), des chaînes, un booléen, un nombre décimal, et un null pour une valeur absente. C'est cette capacité à composer les six types à l'infini qui donne au JSON sa puissance expressive — et c'est aussi, comme on le verra, la source de quelques déboires.

Les pièges de la syntaxe

La grammaire du JSON est minuscule, mais elle a ses pièges. Voici ceux où tout le monde trébuche au moins une fois.

Les clés doivent être entre guillemets doubles. En JavaScript, on peut écrire { nom: "Camille" }. En JSON, c'est une erreur. { "nom": "Camille" } uniquement. C'est la règle qui surprend le plus, parce que notre œil habitué aux objets JavaScript ne voit pas la différence.

Les chaînes n'acceptent que les guillemets doubles. Le guillemet simple, si pratique en Python ou en JavaScript, est interdit. Écrire 'bonjour' dans un JSON, c'est une erreur de parse. Pour qui génère du JSON à la main, c'est la source classique du bug : on oublie d'échapper, on s'étonne.

Pas de commentaires. Vous ne pouvez pas écrire // TODO : revoir ce champ dans un fichier JSON. La spécification ne prévoit rien, et aucun parseur conforme ne l'accepte. Pour documenter un fichier de configuration, il faut un champ dédié, ou changer de format — on y revient dans la section sur JSON5.

Pas de virgule finale. Le joli [1, 2, 3,] qui marche partout en JavaScript est une erreur en JSON. On s'y fait en deux jours, mais les deux premiers jours sont pénibles.

Pas de zéro en tête dans les nombres. 01 est invalide. 0.1 va bien, -0.5 va bien, mais 007 est refusé. Les nombres négatifs, la notation exponentielle (1e10), tout ça est permis ; ce qui est interdit, c'est l'ambiguïté.

-0 et les exposants. Techniquement, -0 est un nombre valide, et certains parseurs le renvoient tel quel, ce qui a déjà fait bugger plus d'une comparaison de signes. Quant à la précision, retenez que JSON ne transporte pas le nombre « exact » : il transporte la représentation décimale, et chaque langage la convertit dans son propre format interne. Un nombre qui tombe juste en texte peut devenir un flottant qui fait des siennes en mémoire.

Les clés dupliquées sont permises par la grammaire, mais leur comportement est indéfini. { "a": 1, "a": 2 } est syntaxiquement valide. Ce que fait le parseur, la spécification ne le dit pas : la plupart gardent la dernière valeur, certains gardent la première, quelques-uns plantent. Autrement dit : ne le faites jamais. Un générateur de JSON qui produit des clés dupliquées, c'est un bug en puissance.

L'Unicode passe par \uXXXX. Un caractère hors ASCII peut être écrit tel quel (le JSON est encodé en UTF-8 par défaut, et le texte se suffit à lui-même), mais la spécification permet aussi l'échappement \u00e9 pour « é ». Pour les caractères hors du plan de base, il faut deux échappements : les paires de substituts, comme \ud83d\ude00 pour l'emoji 😀. Un parseur conforme doit accepter les deux formes ; un générateur a le choix. L'important, c'est que « é » et « \u00e9 » désignent le même caractère, et qu'un outil ne doit pas en inventer un troisième.

Le jeu d'échappements complet, pour finir : \", \\, \/, \b, \f, \n, \r, \t, et \uXXXX. Le \/ est surtout une concession historique (l'échappement des barres dans le HTML), inutile en pratique, mais accepté. Tout autre \x est une erreur : \e, par exemple, n'existe pas.

La bonne nouvelle, c'est que vous n'aurez presque jamais à écrire ces règles de tête : chaque langage a sa bibliothèque, et personne ne parse du JSON à la main depuis 2005. Mais les connaître vous évitera les soirées à comprendre pourquoi votre configuration ne passe pas, ou pourquoi l'API du voisin renvoie une erreur 400 sur un document qui « avait pourtant l'air bon ».

JSON est-il un sous-ensemble de JavaScript ?

Question piège, réponse nuancée. Non, le JSON n'est pas un objet littéral JavaScript. Et c'est même exactement ce qui sépare les deux mondes.

En JavaScript, un objet littéral accepte les guillemets simples, les clés sans guillemets, les virgules finales, et même les commentaires. Rien de tout ça n'est du JSON. En JavaScript, on peut mettre une fonction dans un objet, ou undefined, ou NaN, ou Infinity, ou un objet Date. En JSON, aucun de ces types n'existe. Un JSON.stringify de NaN vous donnera null — le parseur n'a pas le choix, il n'a pas de case pour ça.

L'histoire est amusante. La RFC 4627, en 2006, qualifiait le JSON de sous-ensemble du JavaScript. L'affirmation a ensuite été jugée bancale, pour une raison très concrète : les caractères U+2028 et U+2029 (le séparateur de ligne et le séparateur de paragraphe) sont valides dans une chaîne JSON, mais dans le JavaScript de l'époque, ils étaient bannis du texte source. Si on décidait d'exécuter un JSON comme du code JavaScript, ces caractères faisaient sauter le parseur. D'où une correction, et une posture plus prudente : le JSON ressemble à du JavaScript, il en est syntaxiquement inspiré, mais il s'en distingue précisément là où ça compte.

Pourquoi cette distinction est-elle autre chose qu'une curiosité d'historien ? Parce que la pratique recommandée — et je vais être catégorique — c'est de passer tout JSON reçu du réseau par JSON.parse, jamais par eval. Un objet littéral peut se lire comme du code ; un document JSON ne doit jamais être exécuté. On reparlera dans la section sécurité des raisons historiques de cette règle. Retenez pour l'instant qu'elle tient toujours.

Où vous le croisez chaque jour

Le JSON n'est pas un format de niche. Il est devenu le ciment invisible entre les briques du développement moderne. Petit inventaire, sans prétendre à l'exhaustivité.

Les API REST. Quand votre application appelle fetch('/api/utilisateurs/42'), la réponse est, neuf fois sur dix, un document JSON. La requête elle-même est souvent un JSON dans le corps, notamment pour les POST et PUT. Depuis l'ère des applications à page unique, c'est le format dominant des échanges entre le front et le back — au point qu'on ne réfléchit même plus à la question.

Les fichiers de configuration. Ouvrez n'importe quel projet Node : package.json est du JSON. tsconfig.json, du JSON. Les extensions de VS Code se configurent en JSON. Docker Compose s'écrit en YAML, mais Kubernetes fait du JSON en interne. Même les éditeurs de texte les plus minimalistes finissent par stocker leur configuration en JSON.

Le stockage local du navigateur. localStorage n'accepte que des chaînes. Pour y ranger un objet, la pratique standard, c'est JSON.stringify à l'écriture et JSON.parse à la lecture. IndexedDB, lui, sait stocker des objets directement, mais le JSON reste le format d'échange quand la donnée doit sortir du navigateur.

Les bases de données. PostgreSQL a un type jsonb qui permet de requêter à l'intérieur d'un document JSON avec des opérateurs dédiés. MongoDB, lui, stocke tout en BSON — un format binaire qui est, pour faire simple, du JSON avec des types en plus. Même quand vous n'écrivez pas de JSON de vos mains, la donnée qui transite entre votre application et la base est, elle aussi, une donnée structurée, souvent proche du modèle JSON.

Les logs et le streaming : JSON Lines. Pour les journaux applicatifs, on n'écrit pas un gros JSON de millions de lignes : on écrit une ligne par enregistrement, chaque ligne étant un objet JSON autonome. C'est le format JSON Lines, ou NDJSON. Un fichier de logs, c'est une suite de lignes, chacune parsable indépendamment. C'est aussi le format idéal pour les flux : on envoie une ligne, on en reçoit une, et le consommateur n'a pas à attendre la fin du document.

Les messages WebSocket. Quand deux machines discutent en temps réel — un chat, des cours de bourse, des notifications — le message qui transite est très souvent du JSON. {"type": "message", "contenu": "salut"}. Léger, auto-descriptif, parseable d'un coup d'œil.

Et puis il y a tout ce qu'on ne voit pas : les fichiers de traduction (fr.json, en.json), les exports de données, la réponse de curl que vous collez dans un IDE pour débugger, les payloads entre microservices dans une architecture dite « sans serveur ». Le JSON est le format de l'échange quotidien, celui qu'on ne choisit même plus parce qu'il est déjà là.

Comment chaque langage l'analyse

Le JSON n'a pas de bibliothèque « officielle » au sens d'une implémentation de référence écrite une fois pour toutes. Chaque écosystème a la sienne, et le résultat est très homogène. Petit tour, en restant à la surface.

JavaScript reste le plus simple. Deux fonctions, c'est tout :

const objet = JSON.parse('{"nom": "Camille"}');
const texte = JSON.stringify(objet);

parse lit une chaîne et renvoie la structure ; stringify fait le chemin inverse. Les deux sont dans le runtime, pas besoin d'installer quoi que ce soit. Le piège classique : stringify ne sait pas sérialiser une fonction, un undefined, un Symbol — il les ignore ou les transforme en null, selon la position.

Python a son module json dans la bibliothèque standard. Deux fonctions, loads et dumps :

import json
objet = json.loads('{"nom": "Camille"}')
texte = json.dumps(objet)

Le module convertit vers les types natifs : un objet JSON devient un dict, un tableau une list. Par défaut, json.dumps produit un texte compact ; on passe indent=2 pour un affichage lisible. Et une subtilité que tout le monde découvre un jour : json.loads accepte true et false (minuscules) et les transforme en booléens Python True et False.

Java vit dans un monde plus verbeux, et l'écosystème a tranché depuis longtemps entre deux champions : Jackson et Gson. Jackson, le plus utilisé, s'intègre par annotations :

ObjectMapper mapper = new ObjectMapper();
Utilisateur u = mapper.readValue(texte, Utilisateur.class);
String out = mapper.writeValueAsString(u);

La magie (ou le cauchemar, selon le jour) de Jackson, c'est le mapping réflexif entre le JSON et des classes typées : on décrit une classe Utilisateur, et le JSON se déverse dedans tout seul. Les champs inconnus, les dates, les champs null — tout se règle par annotations ou par configuration globale.

Go est typé et explicite. Le package encoding/json fait la liaison entre JSON et structs :

var u Utilisateur
err := json.Unmarshal([]byte(texte), &u)
out, err := json.Marshal(u)

L'approche Go est intéressante : tout est statique, les champs JSON se lient aux champs de la struct via des tags (json:"nom"), et les erreurs de typage remontent en valeur de retour, qu'on est obligé de traiter. Seule nuance : un champ inconnu est ignoré silencieusement par défaut, à moins de le refuser explicitement avec DisallowUnknownFields. C'est plus de code que le JSON dynamique de JavaScript ou de Python, mais beaucoup moins de surprises.

Rust a fait de serde une religion. Le principe : définir une struct, dériver Serialize et Deserialize, et laisser la bibliothèque faire le reste :

#[derive(Serialize, Deserialize)]
struct Utilisateur { nom: String, age: u8 }
let u: Utilisateur = serde_json::from_str(texte)?;

Ce qui distingue serde, c'est que le mécanisme est générique : la même dérivation fonctionne pour JSON, YAML, MessagePack, et bien d'autres formats. On décrit la structure une fois, et chaque format en tire ce dont il a besoin.

Le fil conducteur, à travers ces cinq écosystèmes : le JSON est l'interface d'échange la plus universelle qui existe. Quel que soit le langage que vous touchez, il a une bibliothèque JSON dans sa bibliothèque standard, ou au prix de deux petites commandes d'installation. C'est peut-être la seule chose qu'ils partagent tous.

Sécurité : des leçons payées au prix fort

Le JSON a un passé que la nouvelle génération de développeurs découvre rarement, et c'est dommage, parce que chaque leçon a été payée par une vraie faille.

La grande époque, c'était le début des années 2000. Les navigateurs n'avaient pas encore fetch, ni un XMLHttpRequest fiable partout, et pour récupérer des données cross-domaine, on utilisait une astuce : injecter une balise <script src="https://api.externe.com/donnees"> dans la page. Le contenu renvoyé s'exécutait comme du code JavaScript, en direct. Pour que ça marche, on ne renvoyait pas du JSON : on renvoyait du JSON enveloppé dans un appel de fonction — c'était JSONP, « JSON with padding », et la page appelait la fonction locale avec les données. Le problème, vous le devinez : le serveur peut répondre n'importe quoi, et la page l'exécute les yeux fermés. Une réponse malveillante, et c'est du code arbitraire qui tourne dans votre page.

Le mal était plus profond. Dans le code de beaucoup d'applications, on trouvait ceci :

const data = eval('(' + response + ')');

Parce que eval était, à l'époque, la seule façon portable de transformer une chaîne en objet JavaScript. Or eval, c'est l'exécution pure et simple : si la réponse contient du code malveillant — et pas seulement des données — il s'exécute. C'est l'exécution de code à distance dans toute sa splendeur, la faille la plus grave qui existe. La règle d'or s'est imposée à la dure : ne jamais, jamais, évaluer du JSON qui vient du réseau. Utiliser un parseur réel. En JavaScript, c'est depuis longtemps réglé : JSON.parse n'exécute rien, il lit. Mais le réflexe mental reste : un document de données n'est pas du code, et ne doit jamais être traité comme tel.

Vient ensuite la question des tableaux en tête de document. Au milieu des années 2000, la découverte d'une faille de type CSRF a fait trembler la communauté : un tableau JSON au niveau supérieur — [{"nom": "Camille"}] — pouvait être lu par un attaquant via une astuce de détournement de tableau JavaScript, même avec une protection CSRF classique. La parade n'a jamais été imposée par la spécification — la RFC autorise n'importe quelle valeur de niveau supérieur — mais elle s'est imposée comme convention : renvoyer un objet {...}, pas un tableau. Les API modernes suivent presque toutes cette convention ; si vous écrivez une API, suivez-la aussi, même si votre cœur balance pour un joli tableau.

Les attaques par prototype pollution ont ajouté leur lot. Un document JSON contenant {"__proto__": {"isAdmin": true}} peut, si le code qui le traite fait un merge naïf des clés dans un objet JavaScript, polluer le prototype et ouvrir des portes. La parade, c'est de ne jamais fusionner des JSON non fiables dans des objets, et d'utiliser des bibliothèques qui refusent les clés __proto__ et constructor.

Enfin, il y a les payloads conçus pour casser la machine qui les lit. Un document imbriqué sur des centaines de niveaux peut faire déborder la pile d'un parseur récursif. Un tableau de millions d'éléments peut faire exploser la mémoire d'un serveur. C'est la « JSON bomb », une variante moderne du zip bomb : un fichier de quelques kilo-octets qui, déplié, représente des gigaoctets. Les bibliothèques sérieuses ont des limites de profondeur et de taille ; les applications sérieuses valident la taille du corps avant même de parser.

La règle moderne, en une phrase : toujours un vrai parseur, jamais eval, des limites de taille, des limites de profondeur, et pas de merge aveugle dans les prototypes. Le JSON n'est pas dangereux en soi — c'est un format de données, il ne fait rien tout seul. Le danger, c'est toujours ce que le code autour en fait.

Validation et extensions : Schema, JSON5

Un document JSON décrit sa propre structure, mais il ne dit rien de ce qui est autorisé ou obligatoire. Pour exprimer un contrat — « ce champ est requis, celui-là est un nombre entier entre 0 et 100, cet autre une date au format ISO » — il faut un schéma.

C'est le rôle de JSON Schema, une spécification (dont les versions se succèdent, la dernière majeure étant la 2020-12) qui permet de décrire la forme attendue d'un document. On y déclare les propriétés obligatoires, les types, les plages, les motifs, les combinaisons logiques. Son rôle est exactement celui que les DTD et XSD jouaient pour XML, en moins solennel. Il a un coût : c'est un outil de plus à apprendre, et des implémentations dans chaque langage. Mais dès qu'une API devient un contrat entre deux équipes, ou dès qu'on veut valider une configuration avant de l'appliquer, c'est presque toujours la bonne réponse.

Côté pragmatique, la vie réelle a produit des extensions qui assouplissent la syntaxe stricte. La plus connue est JSON5, « JSON for Humans », qui autorise les commentaires, les virgules finales, les clés sans guillemets, les guillemets simples, et des valeurs comme Infinity ou NaN. Le nom dit tout : c'est du JSON qu'on écrit à la main, pour les humains, au prix d'un parseur dédié et d'une perte de compatibilité avec les outils standards. Aucun organisme de normalisation ne l'a adoptée, et c'est voulu.

Il y a aussi JSONC, l'abréviation courante pour « JSON avec commentaires », qu'on trouve dans les fichiers de configuration des éditeurs (VS Code en fait grand usage). Le principe : la grammaire JSON, plus les commentaires // et /* */. Tout le monde sait que c'est du bricolage, mais le bricolage fonctionne et fait avancer les choses.

Et HJSON, moins répandu, qui pousse l'idée plus loin : pas de virgules, pas de guillemets quand ce n'est pas nécessaire, une syntaxe proche de ce que l'œil humain trouve naturel.

Ces extensions partagent un point commun : elles ne sont pas des standards, et leurs auteurs le disent eux-mêmes. Ce sont des compromis assumés, des raccourcis pragmatiques pour le cas « un humain écrit un fichier de configuration à la main ». Dès qu'un outil standard doit relire le fichier, il faut revenir au JSON strict. La ligne de partage est nette, et il faut la garder en tête.

Ses limites et les griefs

Le JSON a vingt-cinq ans de succès, et vingt-cinq ans de griefs accumulés. Les habitués les connaissent par cœur ; les nouveaux arrivants les découvrent en tombant dedans.

Pas de commentaires. Pour un format né au cœur des configurations de logiciels, c'est la frustration numéro un. On veut documenter pourquoi un champ existe, annoter une valeur qui semble magique, et la grammaire refuse. Résultat : des fichiers de configuration truffés de champs _commentaire, ou des migrations vers YAML quand la documentation devient vitale.

Pas de type date. Le JSON ne connaît pas la date. Chaque équipe réinvente donc la sienne : des chaînes ISO-8601 dans le meilleur des cas, des timestamps en secondes ou en millisecondes selon l'humeur, des formats maison dont plus personne ne se souvient exactement. D'où la prolifération de bibliothèques de gestion de dates (date-fns, moment, Joda, java.time) qui passent une bonne partie de leur vie à convertir des chaînes en dates et inversement. Le manque n'est pas rédhibitoire, mais il est réel, et il se paye en code de conversion écrit et réécrit.

La précision des nombres. Un nombre JSON est transporté comme du texte décimal, mais chaque langage le convertit dans son format natif. Le standard de fait, c'est le double IEEE-754 : environ quinze à dix-sept chiffres décimaux significatifs. Pour un identifiant à vingt chiffres, pour un montant financier, pour une précision qui doit être exacte, c'est trop court. Une API qui renvoie un id de 20 chiffres à JavaScript le transformera en double, et le dernier chiffre sera perdu. Les banques, qui détestent ce genre de surprises, ont appris à tout passer en chaînes. Vous aussi, si vous manipulez des grands entiers, faites-le.

Pas de binaire. Pour envoyer un fichier, une image, n'importe quel octet, le JSON n'a pas de case. La parade universelle, c'est le base64 : on encode le binaire en texte, on le met dans une chaîne, on décode de l'autre côté. Le coût, c'est environ 33 % d'octets en plus, et une gêne permanente quand on réalise qu'on a emballé des mégaoctets dans un format pensé pour du texte.

L'imbrication profonde fait sauter la pile. Un parseur récursif qui descend dans un document à mille niveaux d'imbrication peut mourir d'un dépassement de pile. Les parseurs modernes ont des limites, mais le problème demeure, et il faut y penser quand on génère du JSON arbitrairement imbriqué.

Les clés dupliquées, déjà évoquées. Permises, mais au comportement indéfini. Si vous générez du JSON par concaténation de bouts de chaînes — ce qui arrive encore — vous pouvez produire des doublons sans le voir.

La roue réinventée. Pas de date, pas de décimal exact, pas d'octets, pas de références entre objets : chaque besoin laissé de côté est redéfini par chaque équipe, chaque API, chaque format dérivé. Le JSON gagne en universalité ce qu'il perd en expressivité. Et quand un besoin revient trop souvent, quelqu'un finit par inventer une variante — ce qui nous amène à la question des alternatives.

Les alternatives

Le JSON n'est pas le seul format de données, et il ne l'a jamais été. Les alternatives se classent en trois familles : les formats lisibles par les humains, les formats compacts, et les formats typés.

YAML est le cousin lisible par les humains. Il supprime les accolades, les virgules, souvent les guillemets, et produit des fichiers qui ressemblent à de la documentation. C'est son charme et son piège : sa grammaire est notoirement retorse. L'indentation porte du sens, les tableaux peuvent s'écrire de trois façons différentes, et certaines chaînes — yes, no, on, off, des nombres comme 1e3 — sont interprétées d'une manière qui surprend. Le cas le plus fameux est le « Norway problem » : no devenant un booléen dans certains parseurs. YAML a sa place pour les configurations écrites à la main (Ansible, GitLab CI, Docker Compose), mais dès qu'un outil génère du YAML, c'est une source de problèmes. Pour des données échangées entre machines, c'est rarement le bon choix.

TOML est le configurateur discipliné. Pensé pour les fichiers de configuration, il a une syntaxe simple : cle = valeur, des tableaux avec [[...]], des types explicites. Pas d'ambiguïté, pas de piège d'indentation, et un typage honnête. Cargo (Rust) et les configurations Python modernes l'ont adopté. Sa limite : il n'est pas fait pour des documents profondément imbriqués ni pour de la donnée hiérarchique riche. Pour une configuration, parfait ; pour un payload d'API, non.

Protocol Buffers, chez Google, est le champion des formats typés. On décrit le message dans un fichier .proto, on génère du code dans chaque langage, et la sérialisation est compacte, typée, versionnée. C'est la solution pour les communications internes à très haut débit, quand la performance et la compatibilité de schéma comptent plus que la lisibilité. Le coût : un schéma obligatoire, une étape de génération de code, et une lisibilité humaine proche de zéro. On ne lit pas un protobuf dans un éditeur de texte.

MessagePack et CBOR sont le JSON en binaire. Même modèle de données, même simplicité, mais encodés en octets : plus compacts, plus rapides à parser. Le prix, c'est l'illisibilité : pour débugger, il faut un outil. Quand la bande passante ou la latence comptent, et qu'on n'a pas besoin d'un schéma, ce sont des choix honnêtes. BSON, la variante de MongoDB, fait la même chose avec quelques types en plus (comme les dates et les binaires) — c'est le format interne de la base, et c'est pour ça qu'il vous épargne les conversions de dates dans les deux sens.

Le choix, en une phrase : le JSON reste le format de l'échange généraliste, celui qu'on prend par défaut parce qu'il est lisible, universel, et sans cérémonial. On change quand on a une raison précise — un schéma obligatoire, un besoin de performance, une configuration à écrire à la main. Et on accepte alors le coût correspondant.

En guise de conclusion : les formats ennuyeux durent

Vingt-cinq ans. Le JSON est sorti à peu près au moment où les services web SOAP s'effondraient sous leur propre poids, et il est toujours là quand on parle d'API REST, de microservices, de configurations, de logs, de bases de données. C'est un format sans ambition : pas de schéma obligatoire, pas de types riches, pas de compression. Et c'est exactement pour ça qu'il gagne.

Un format de données dure quand il est assez bon pour le plus grand nombre de cas, présent partout, et si simple qu'aucune décision n'est nécessaire pour l'adopter. Le JSON est le format « par défaut » par excellence : quand on n'a aucune raison de choisir autre chose, on prend le JSON, et personne ne pose de question. Cette absence de friction vaut tous les raffinements du monde.

Il faut évidemment savoir le quitter quand il le faut. Pour une configuration qui exige de la documentation, prenez YAML ou TOML. Pour des millions de messages par seconde entre serveurs, passez aux buffers ou au binaire. Pour un contrat d'API à vingt équipes, imposez un schéma. Le JSON n'est pas une fin en soi, et personne ne demande qu'on l'adore.

Ce qui est vrai, c'est que les formats ennuyeux durent. Les choses simples, suffisantes, sans panache, survivent aux modes parce qu'elles ne dépendent d'aucune d'elles. Le JSON n'est pas élégant, il n'est pas performant, il n'est pas typé. Il est partout, et ça, c'est une propriété que les formats sophistiqués n'achètent pas. Quand votre API à deux microservices devra encore tourner dans dix ans, elle parlera probablement toujours JSON — non par nostalgie, mais parce que rien d'autre n'aura été assez ennuyeux pour remplacer le format qu'on ne choisit même plus.