JSON binaire : choisir entre MessagePack, CBOR et BSON
L’essentiel : MessagePack, CBOR et BSON compressent tous JSON en octets. Comparez le coût en octets, la gestion d'int64 et du binaire, et obtenez une ligne claire pour savoir quand changer vaut le coup.

L'essentiel : gardez le JSON texte pour l'échange externe et pour les données que des gens doivent inspecter, et passez à MessagePack ou CBOR sur les liens internes seulement quand les charges binaires, les entiers au-delà de 2^53 ou les décimaux exacts rendent la taille compressée ou l'expressivité des types réellement problématique ; BSON appartient à l'écosystème MongoDB.
Le JSON texte économise les espaces ; les formats binaires économisent la ponctuation et la forme textuelle des nombres. Les noms de champs apparaissent encore une fois par enregistrement, donc calculez le gain réel avant de changer.
1. Ce que le JSON binaire économise réellement
Fixez d'abord les attentes. Aucun de ces formats n'est « une autre syntaxe JSON ». Ils encodent le même modèle de données en octets : objets, tableaux, chaînes, nombres, booléens et null. Ce qu'ils suppriment est concret :
- Les caractères structurels comme guillemets, deux-points, virgules et accolades ;
- La forme décimale textuelle des nombres, donc
32devient un seul octet ; - Le rebalayage pour les frontières de chaînes, remplacé par un préfixe de longueur fixe ;
- L'encodage Base64 des charges binaires, qui peuvent être stockées directement en octets.
Ce qu'ils ne peuvent pas supprimer est tout aussi concret : les noms de champs apparaissent encore comme des chaînes dans chaque enregistrement. Une map MessagePack, une map CBOR et un document BSON répètent tous la clé pour chaque entrée. Donc quand votre JSON est dix mille enregistrements de même forme, l'écart de taille compressée entre texte et binaire est bien plus petit que l'intuition ne le suggère, car gzip excelle à éliminer les noms de champs répétés.
2. D'où viennent les trois formats
| Format | Spécification et année | Origine | Usage typique |
|---|---|---|---|
| MessagePack | Spécification msgpack.org, depuis 2008 | Conception indépendante axée sur le minimalisme | Valeurs de cache, IPC, pipelines de journaux |
| CBOR | RFC 8949 (STD 94), 2020 | Influencé par MessagePack, normalisé par l'IETF | COSE, WebAuthn, CWT, appareils contraints |
| BSON | bsonspec.org, écosystème MongoDB | Conçu pour les documents de base de données | Stockage et protocole filaire MongoDB |
CBOR est le plus normalisé : un type de média application/cbor enregistré à l'IANA, des règles d'encodage déterministes, et CDDL pour la description. MessagePack est le plus léger, avec une spécification qui tient sur une page. BSON est le plus lourd : il intègre ObjectId, Decimal128, dates et sous-types binaires dans son système de types, ce qui explique pourquoi il n'est vraiment populaire que dans l'écosystème MongoDB.
3. Les mêmes données, quatre encodages
Prenons {"name":"joe"} comme exemple. Les coûts en octets sont faciles à comparer :
| Encodage | Octets | Séquence d'octets |
|---|---|---|
| Texte JSON | 14 | 7b 22 6e 61 6d 65 22 3a 22 6a 6f 65 22 7d |
| MessagePack | 9 | 81 a3 6e 61 6d 65 a3 6a 6f 65 |
| CBOR | 10 | a1 64 6e 61 6d 65 63 6a 6f 65 |
| BSON | 19 | longueur totale de 4 octets + octet de type + name\0 + longueur de valeur de 4 octets + joe\0 + terminateur |
Décomposons les neuf octets MessagePack :
81: le demi-octet haut1000signifie map, le demi-octet bas0001signifie une paire clé-valeur ;a3: les trois bits hauts101signifient une chaîne de longueur fixe 3 ;6e 61 6d 65: ASCII pourname;a3: une autre chaîne de longueur 3 ;6a 6f 65:joe.
Le a1 de CBOR est le type majeur 5 (map) de longueur 1, et 64 est le type majeur 3 (chaîne de texte) de longueur 4, donc la sémantique est presque isomorphe à MessagePack. BSON est plus gros : chaque document porte une longueur totale de 4 octets, chaque champ répète un octet de type et son nom, et chaque chaîne répète une longueur plus un \0 final. Sur de petits documents, BSON peut facilement être plus gros que le texte JSON.
Vous pouvez vérifier les préfixes directement en rendant ces octets en hexadécimal :
const messagePackBytes = [0x81, 0xa3, 0x6e, 0x61, 0x6d, 0x65, 0xa3, 0x6a, 0x6f, 0x65];
const hexText = messagePackBytes.map((byte) => byte.toString(16)).join(' ');
console.log(hexText); // 81 a3 6e 61 6d 65 a3 6a 6f 65
3.1 Taille et vitesse de parsing : ignorez les chiffres marketing
Les affirmations comme « le binaire est 10 fois plus rapide » doivent être scindées en deux questions.
Taille. Non compressé, le binaire est plus petit car ponctuation et espaces ont disparu. Mais les vrais pipelines compressent presque toujours : après gzip ou brotli, les noms de champs répétés sont compressés, et l'avantage du binaire se réduit drastiquement. Suivez la méthode de minification JSON contre gzip : mesurez d'abord les octets compressés, puis décidez si un changement de format en vaut le coup.
Vitesse de parsing. Le binaire évite le lexing (balayage des guillemets, virgules et caractères de nombres), donc il devrait être plus rapide en théorie. En JavaScript, cependant, le coût dominant est l'allocation d'objets et de mémoire, qu'aucun décodeur ne peut éviter. La forme des données, le nombre de champs et la qualité de l'implémentation comptent tous, donc la seule conclusion fiable vient d'un benchmark de vos propres charges utiles plutôt que du multiplicateur d'un fournisseur. Si une seule charge utile est trop grosse pour être lue en mémoire d'un coup, découpez-la avec l'approche décrite dans parser et diffuser de grands JSON avant de mesurer le gain d'encodage.
4. int64 et champs binaires : la vraie raison de changer
La taille n'est souvent pas la raison de changer de format. L'expressivité des types l'est.
4.1 Entiers au-delà de 2^53
Les nombres JSON atterrissent dans un double en JavaScript, donc 9007199254740993 devient 9007199254740992. Les trois formats binaires gèrent cela différemment :
| Format | Entiers 64 bits | 64 bits non signé | Précision arbitraire |
|---|---|---|---|
| MessagePack | ✓ int64 (0xd3), décodable en BigInt |
✓ uint64 (0xcf) |
✗ (nécessite un type d'extension) |
| CBOR | ✓ formes 64 bits des types majeurs 0/1 | ✓ Même espace d'encodage | ✓ balises bignum 2/3 |
| BSON | ✓ int64 (0x12) |
✗ Signé seulement | ✗ (Decimal128 est à virgule fixe, pas un entier arbitraire) |
CBOR est le seul à disposer d'une représentation standard pour les entiers de précision arbitraire : les valeurs au-delà de 64 bits sont enveloppées dans la balise 2 (non négatif) ou 3 (négatif) autour d'une chaîne d'octets. MessagePack et BSON retombent sur des chaînes ou des conventions d'extension sur mesure.
4.2 Charges binaires
JSON n'a qu'une façon d'exprimer le binaire : Base64, qui gonfle la charge utile d'environ un tiers et exige une copie de décodage supplémentaire. MessagePack a des types bin (0xc4–0xc6), CBOR a des chaînes d'octets (type majeur 2), et BSON a des sous-types binaires. Quand vous transportez des images, hachages, vecteurs ou blocs compressés, cette différence compte bien plus que d'économiser quelques octets de ponctuation.
5. Comparaison côte à côte
| Dimension | MessagePack | CBOR | BSON |
|---|---|---|---|
| Spécification | Spécification communautaire | RFC IETF 8949 | Écosystème MongoDB |
| Type de média | application/msgpack (non standard IANA) |
application/cbor (enregistré IANA) |
Aucun type de média général |
| Système de types | Six types JSON plus bin et ext | Six types JSON plus bin, balises et valeurs simples | Plus large : ObjectId, Decimal128, dates, regex, horodatages |
| Mécanisme d'extension | Types d'extension (par ex. timestamp -1) | Balises (dates, bignums, CBOR embarqué) | Codes de type fixes, extensions par convention d'écosystème |
| Encodage déterministe | Aucune exigence standard | Règles explicites, utilisables pour les signatures | Aucun |
| Auto-descriptif | Oui, mais pas de schéma au niveau des champs | Oui, et descriptible avec CDDL | Oui, avec noms de champs répétés par document |
| Surcoût des petits documents | Faible | Faible | Élevé (préfixe de longueur, noms de champs, terminateur) |
| Écosystème | Caches, journaux, certains RPC | COSE, WebAuthn, IoT | MongoDB seulement |
6. Support navigateur et Node
Node 24 n'embarque aucun de ces codecs, donc vous installez une bibliothèque. Choix courants :
| Runtime | MessagePack | CBOR | BSON |
|---|---|---|---|
| Node.js | @msgpack/msgpack, msgpackr |
cbor-x, borc |
bson |
| Navigateur | @msgpack/msgpack |
Build navigateur de cbor-x, cbor-web |
bson |
Les deux familles fonctionnent sur Uint8Array et n'exigent pas Buffer. Surveillez les différences de version :
import { encode, decode } from '@msgpack/msgpack';
/**
* Encodes an object to MessagePack bytes and decodes it immediately for verification.
* @param payload Plain object to encode
* @returns The decoded value and the encoded byte length
*/
function roundTrip(payload: Record<string, unknown>): { decoded: unknown; byteLength: number } {
const bytes = encode(payload);
return { decoded: decode(bytes), byteLength: bytes.byteLength };
}
La plupart des bibliothèques décodent les grands entiers en number par défaut et perdent la précision au-delà de la plage sûre. Si les valeurs exactes comptent, activez explicitement l'option BigInt (useBigInt64 dans @msgpack/msgpack, un interrupteur équivalent dans cbor-x), sinon vous avez simplement déplacé l'ancien problème de JSON vers un nouveau format.
7. Débogabilité et compatibilité de protocole
7.1 Débogabilité : le plus grand coût caché
Le plus grand avantage du JSON texte n'est pas la taille ; c'est que vous pouvez l'ouvrir à tout moment. Après passage au binaire :
grep,awketjqcessent tous de fonctionner, et les pipelines de journaux exigent une étape de décodage supplémentaire ;- Déboguer la production exige le bon décodeur à la bonne version, sinon les noms de champs que vous voyez seront faux ;
- Le trafic capturé s'affiche dans les outils développeur du navigateur comme un blob d'octets illisible ;
- Les diffs de revue de code montrent une chaîne d'hexadécimal.
CBOR a une notation de diagnostic, qui rend les octets en texte lisible proche de JSON, par exemple une map comme {1: 2} ; MessagePack a aussi quelques décodeurs en ligne de commande. Mais tous exigent une installation supplémentaire et se situent hors du chemin d'exploitation standard.
La conclusion : chaque fois que des gens doivent inspecter fréquemment les données, le coût caché d'un format binaire dépasse la bande passante qu'il économise.
7.2 Compatibilité de protocole et extensions
- CBOR a le mécanisme de balises le plus complet : balise 0 pour une chaîne date-heure, balise 1 pour un horodatage Unix, balises 2 et 3 pour les bignums, et balise 24 pour du CBOR embarqué. Combiné à l'encodage déterministe, il est utilisé par des protocoles qui exigent signatures et cohérence inter-implémentations, comme COSE, WebAuthn et CWT.
- MessagePack laisse les types d'extension aux implémenteurs. En pratique le seul courant est timestamp (type d'extension -1). Entre équipes, chaque type d'extension doit être écrit dans le document de protocole, sinon l'autre côté ne peut pas décoder.
- BSON a des codes de type fixes, et
ObjectId,Decimal128,MinKeyetMaxKeysont des sémantiques spécifiques à MongoDB. Traiter BSON comme un format d'échange général apporte un tas de types inutilisés et de préfixes de longueur inutilisés.
Si un protocole exige « les mêmes données doivent produire les mêmes octets », seul CBOR fournit une réponse standard ; MessagePack et BSON ont tous deux besoin d'un accord supplémentaire sur l'ordre des champs et les détails d'encodage.
8. Quand le changement en vaut le coup
Signaux qui justifient un changement :
- Les charges utiles sont grosses, les deux extrémités peuvent changer, et les tailles compressées diffèrent encore de façon significative ;
- Vous transportez du contenu binaire et le gonflement Base64 et le coût de décodage sont mesurables ;
- Vous avez besoin d'entiers 64 bits ou de décimaux exacts que les doubles JSON ne peuvent pas représenter ;
- Le lien est serveur-à-serveur et l'outillage de diagnostic peut être standardisé.
Signaux qui plaident contre :
- gzip ou brotli est déjà en place et le gain est d'un pourcentage à un chiffre ;
- L'API est publique et les tiers n'ont que des clients HTTP généralistes ;
- Les données sont des journaux ou de la configuration que des gens lisent à la main ;
- L'équipe n'a pas de budget pour maintenir décodeurs et compatibilité de version.
Une voie intermédiaire fonctionne aussi : gardez JSON en externe et utilisez MessagePack pour les caches internes. Les valeurs de cache ne sont lues que par vos propres services, donc taille et coût de parsing s'améliorent, et vider le cache résout tout problème sans toucher au contrat.
9. Liste de vérification
- [ ] Comparez les nombres d'octets compressés de données réelles, pas les nombres non compressés ;
- [ ] Décidez la politique des grands entiers : option BigInt, transport en chaîne, ou bignums CBOR ;
- [ ] Envoyez les champs binaires comme bin ou chaînes d'octets au lieu de les envelopper en Base64 ;
- [ ] Écrivez les types d'extension et les balises dans le document de protocole et épinglez les versions de décodeur ;
- [ ] Gardez un chemin de débogage en JSON texte activable pendant les incidents de production ;
- [ ] Lancez un benchmark et enregistrez des chiffres reproductibles plutôt que des affirmations verbales.
Quelle est la différence entre MessagePack et CBOR ?
Leurs encodages sont très similaires : maps, tableaux et chaînes utilisent tous un préfixe compact type-plus-longueur. La différence principale est la normalisation. CBOR est une RFC IETF avec un type de média IANA, un mécanisme de balises et des règles d'encodage déterministes ; MessagePack est une spécification communautaire dont les types d'extension reposent sur des accords privés. Choisissez CBOR quand il faut des signatures, une cohérence inter-implémentations ou des protocoles pour appareils contraints.
BSON peut-il remplacer JSON comme format général ?
Non recommandé. BSON a été conçu pour les documents de base de données : chaque document porte une longueur totale de 4 octets, chaque champ répète son type et son nom, et les petits documents finissent plus gros que le texte JSON. Il inclut aussi des types spécifiques à MongoDB comme ObjectId et MinKey. Hors de MongoDB, la plupart de son système de types est inutilisé tandis que la pénalité de taille demeure.
De combien un format binaire réduit-il ma charge utile ?
Cela dépend de la forme des données. Quand les noms de champs se répètent fortement, le texte JSON compressé et les formats binaires finissent proches ; quand les noms sont courts et que les données regorgent de caractères structurels et de nombres, l'avantage du binaire est plus net. Mesurez de vrais échantillons non compressés et après gzip, puis décidez, plutôt que d'estimer avec un multiplicateur générique.
Peut-on encore déboguer des données binaires avec jq ?
Non. jq ne traite que du JSON texte et échouera immédiatement sur des octets MessagePack ou CBOR. Il faut décoder avec la bibliothèque correspondante en JSON d'abord puis le passer à jq ; CBOR peut aussi passer par un outil de notation de diagnostic pour devenir du texte lisible. Garder un chemin de débogage texte coûte moins que d'installer l'outillage à la demande.