JSON binário: escolhendo entre MessagePack, CBOR e BSON
Resumo: MessagePack, CBOR e BSON todos comprimem JSON em bytes. Compare custo de bytes, tratamento de int64 e binário, e obtenha uma linha clara de quando vale a pena trocar.

Resumo da ópera: mantenha JSON de texto para troca externa e para dados que pessoas precisam inspecionar, e mude para MessagePack ou CBOR em links internos apenas quando payloads binários, inteiros acima de 2^53 ou decimais exatos tornarem o tamanho comprimido ou a expressividade de tipos um problema real; o BSON pertence ao ecossistema MongoDB.
O JSON de texto economiza espaços em branco; formatos binários economizam a pontuação e a forma textual dos números. Nomes de campo ainda aparecem uma vez por registro, então calcule a economia real antes de trocar.
1. O que o JSON binário realmente economiza
Defina as expectativas primeiro. Nenhum desses formatos é "outra sintaxe JSON". Eles codificam o mesmo modelo de dados em bytes: objetos, arrays, strings, números, booleanos e null. O que eles removem é concreto:
- Caracteres estruturais como aspas, dois-pontos, vírgulas e chaves;
- A forma textual decimal dos números, então
32vira um único byte; - A reanálise para encontrar fronteiras de string, substituída por um prefixo de comprimento fixo;
- A codificação Base64 de payloads binários, que podem ser armazenados como bytes diretamente.
O que eles não conseguem remover é igualmente concreto: nomes de campo ainda aparecem como strings em todo registro. Um map MessagePack, um map CBOR e um documento BSON todos repetem a chave para cada entrada. Então, quando seu JSON são dez mil registros com a mesma forma, a diferença de tamanho comprimido entre texto e binário é bem menor do que a intuição sugere, porque o gzip é melhor justamente em eliminar nomes de campo repetidos.
2. De onde vêm os três formatos
| Formato | Especificação e ano | Origem | Uso típico |
|---|---|---|---|
| MessagePack | Especificação msgpack.org, desde 2008 | Design independente focado em minimalismo | Valores de cache, IPC, pipelines de log |
| CBOR | RFC 8949 (STD 94), 2020 | Influenciado pelo MessagePack, padronizado pela IETF | COSE, WebAuthn, CWT, dispositivos restritos |
| BSON | bsonspec.org, ecossistema MongoDB | Projetado para documentos de banco de dados | Armazenamento e protocolo de transporte do MongoDB |
O CBOR é o mais padronizado: tipo de mídia application/cbor registrado na IANA, regras de codificação determinística e CDDL para descrição. O MessagePack é o mais leve, com uma especificação que cabe em uma página. O BSON é o mais pesado: ele embute ObjectId, Decimal128, datas e subtipos binários em seu sistema de tipos, o que explica por que ele só é realmente popular dentro do ecossistema MongoDB.
3. Os mesmos dados, quatro codificações
Pegue {"name":"joe"} como exemplo. Os custos de bytes são fáceis de comparar:
| Codificação | Bytes | Sequência de bytes |
|---|---|---|
| Texto 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 | Comprimento total de 4 bytes + byte de tipo + name\0 + comprimento do valor de 4 bytes + joe\0 + terminador |
Separe os nove bytes do MessagePack:
81: o nibble alto1000significa map, o nibble baixo0001significa um par chave-valor;a3: os três bits altos101significam uma string de comprimento fixo 3;6e 61 6d 65: ASCII paraname;a3: outra string de comprimento 3;6a 6f 65:joe.
O a1 do CBOR é tipo maior 5 (map) com comprimento 1, e 64 é tipo maior 3 (string de texto) com comprimento 4, então a semântica é quase isomórfica à do MessagePack. O BSON é maior: todo documento carrega um comprimento total de 4 bytes, todo campo repete um byte de tipo e seu nome, e toda string repete um comprimento mais um \0 final. Em documentos pequenos, o BSON pode facilmente ser maior que o texto JSON.
Você pode verificar os prefixos diretamente renderizando esses bytes como hexadecimal:
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 Tamanho e velocidade de análise: ignore os números de marketing
As afirmações de que o binário é "10x mais rápido" devem ser divididas em duas perguntas.
Tamanho. Sem compressão, o binário é menor porque a pontuação e os espaços em branco somem. Mas pipelines reais quase sempre comprimem: após gzip ou brotli, nomes de campo repetidos são comprimidos e a vantagem do binário encolhe drasticamente. Siga o método em Minificação JSON versus gzip: meça os bytes comprimidos primeiro e depois decida se mudar de formato vale a pena.
Velocidade de análise. O binário pula a lexing (varredura de aspas, vírgulas e caracteres numéricos), então teoricamente deve ser mais rápido. Em JavaScript, no entanto, o custo dominante é alocar objetos e memória, o que nenhum decodificador consegue evitar. Forma dos dados, número de campos e qualidade da implementação importam, então a única conclusão confiável vem de medir seus próprios payloads em vez de citar o multiplicador de um fornecedor. Se um payload único for grande demais para ser lido na memória de uma vez, fatie-o com a abordagem descrita em Análise e streaming de JSON grande antes de medir o ganho de codificação.
4. int64 e campos binários: a verdadeira razão para trocar
Tamanho muitas vezes não é a razão para mudar de formato. A expressividade de tipos é.
4.1 Inteiros acima de 2^53
Números JSON caem em um double em JavaScript, então 9007199254740993 vira 9007199254740992. Os três formatos binários tratam isso de formas diferentes:
| Formato | Inteiros de 64 bits | 64 bits sem sinal | Precisão arbitrária |
|---|---|---|---|
| MessagePack | ✓ int64 (0xd3), decodificável para BigInt |
✓ uint64 (0xcf) |
✗ (precisa de um tipo de extensão) |
| CBOR | ✓ formas de 64 bits dos tipos maiores 0/1 | ✓ Mesmo espaço de codificação | ✓ tags bignum 2/3 |
| BSON | ✓ int64 (0x12) |
✗ Apenas com sinal | ✗ (Decimal128 é ponto fixo, não um inteiro arbitrário) |
O CBOR é o único com uma representação padrão para inteiros de precisão arbitrária: valores acima de 64 bits são envolvidos na tag 2 (não negativo) ou tag 3 (negativo) em torno de uma string de bytes. MessagePack e BSON recaem em strings ou convenções de extensão próprias.
4.2 Payloads binários
O JSON tem exatamente uma forma de expressar binário: Base64, que infla o payload em cerca de um terço e exige uma cópia extra de decodificação. O MessagePack tem tipos bin (0xc4–0xc6), o CBOR tem strings de bytes (tipo maior 2) e o BSON tem subtipos binários. Quando você transporta imagens, hashes, vetores ou blocos comprimidos, essa diferença importa muito mais do que economizar alguns bytes de pontuação.
5. Comparação lado a lado
| Dimensão | MessagePack | CBOR | BSON |
|---|---|---|---|
| Especificação | Especificação da comunidade | RFC 8949 da IETF | Ecossistema MongoDB |
| Tipo de mídia | application/msgpack (não padrão IANA) |
application/cbor (registrado na IANA) |
Sem tipo de mídia geral |
| Sistema de tipos | Seis tipos JSON mais bin e ext | Seis tipos JSON mais bin, tags e valores simples | Mais amplo: ObjectId, Decimal128, datas, regex, timestamps |
| Mecanismo de extensão | Tipos de extensão (por exemplo timestamp -1) | Tags (datas, bignums, CBOR embutido) | Códigos de tipo fixos, extensões por convenção do ecossistema |
| Codificação determinística | Sem exigência padrão | Regras explícitas, utilizáveis para assinaturas | Nenhuma |
| Autodescritivo | Sim, mas sem schema em nível de campo | Sim, e descritível com CDDL | Sim, com nomes de campo repetidos por documento |
| Overhead de documento pequeno | Baixo | Baixo | Alto (prefixo de comprimento, nomes de campo, terminador) |
| Ecossistema | Caches, logs, algum RPC | COSE, WebAuthn, IoT | Apenas MongoDB |
6. Suporte em navegador e Node
O Node 24 não traz nenhum desses codecs, então você instala uma biblioteca. Escolhas comuns:
| Runtime | MessagePack | CBOR | BSON |
|---|---|---|---|
| Node.js | @msgpack/msgpack, msgpackr |
cbor-x, borc |
bson |
| Navegador | @msgpack/msgpack |
Build de navegador do cbor-x, cbor-web |
bson |
Ambas as famílias funcionam em Uint8Array e não exigem Buffer. Cuidado com as diferenças de versão:
import { encode, decode } from '@msgpack/msgpack';
/**
* Codifica um objeto em bytes MessagePack e o decodifica imediatamente para verificação.
* @param payload Objeto simples a codificar
* @returns O valor decodificado e o comprimento de bytes codificado
*/
function roundTrip(payload: Record<string, unknown>): { decoded: unknown; byteLength: number } {
const bytes = encode(payload);
return { decoded: decode(bytes), byteLength: bytes.byteLength };
}
A maioria das bibliotecas decodifica inteiros grandes para number por padrão e perde precisão além da faixa segura. Se valores exatos importam, habilite a opção BigInt explicitamente (useBigInt64 em @msgpack/msgpack, um switch equivalente no cbor-x), caso contrário você simplesmente mudou o velho problema do JSON para um formato novo.
7. Depurabilidade e compatibilidade de protocolo
7.1 Depurabilidade: o maior custo oculto
A maior vantagem do JSON de texto não é o tamanho; é que você pode abri-lo a qualquer momento. Após mudar para binário:
grep,awkejqtodos param de funcionar, e pipelines de log precisam de uma etapa extra de decodificação;- Depurar produção exige o decodificador certo na versão certa, ou os nomes de campo que você vê estarão errados;
- O tráfego capturado aparece nas ferramentas de desenvolvedor do navegador como um blob de bytes ilegível;
- Diffs de revisão de código mostram uma sequência de hexadecimal.
O CBOR tem notação diagnóstica, que renderiza bytes de volta em texto legível próximo de JSON, por exemplo um map como {1: 2}; o MessagePack também tem alguns decodificadores de linha de comando. Mas todos exigem instalação extra e ficam fora do caminho padrão de operação.
A conclusão: sempre que pessoas precisam inspecionar os dados com frequência, o custo oculto de um formato binário supera a banda que ele economiza.
7.2 Compatibilidade de protocolo e extensões
- CBOR tem o mecanismo de tags mais completo: tag 0 para uma string de data-hora, tag 1 para um timestamp Unix, tags 2 e 3 para bignums e tag 24 para CBOR embutido. Combinado com codificação determinística, é usado por protocolos que precisam de assinaturas e consistência entre implementações, como COSE, WebAuthn e CWT.
- MessagePack deixa os tipos de extensão para os implementadores. Na prática, o único comum é timestamp (tipo de extensão -1). Entre equipes, todo tipo de extensão precisa estar escrito no documento de protocolo ou a outra ponta não consegue decodificar.
- BSON tem códigos de tipo fixos, e
ObjectId,Decimal128,MinKeyeMaxKeysão semânticas específicas do MongoDB. Tratar o BSON como formato geral de intercâmbio traz um monte de tipos não usados e prefixos de comprimento não usados.
Se um protocolo exige que "os mesmos dados produzam os mesmos bytes", só o CBOR fornece uma resposta padrão; MessagePack e BSON precisam ambos de acordo extra sobre ordem de campos e detalhes de codificação.
8. Quando vale a pena trocar
Sinais que justificam a troca:
- Os payloads são grandes, as duas pontas podem mudar e os tamanhos comprimidos ainda diferem de forma significativa;
- Você transporta conteúdo binário e a inflação do Base64 e o custo de decodificação são mensuráveis;
- Você precisa de inteiros de 64 bits ou decimais exatos que os doubles JSON não conseguem representar;
- O link é servidor a servidor e a ferramenta de diagnóstico pode ser padronizada.
Sinais que argumentam contra:
- O gzip ou brotli já está em vigor e o ganho é de um dígito percentual;
- A API é pública e os terceiros só têm clientes HTTP generalistas;
- Os dados são logs ou configuração que pessoas leem à mão;
- A equipe não tem orçamento para manter decodificadores e compatibilidade de versão.
Um caminho intermediário também funciona: mantenha JSON externamente e use MessagePack para caches internos. Valores de cache são lidos apenas pelos seus próprios serviços, então tanto tamanho quanto custo de análise melhoram, e limpar o cache resolve qualquer problema sem tocar no contrato.
9. Lista de verificação
- [ ] Compare contagens de bytes comprimidos de dados reais, não contagens sem compressão;
- [ ] Decida a política de inteiros grandes: opção BigInt, transporte por string ou bignums CBOR;
- [ ] Envie campos binários como bin ou strings de bytes em vez de envolvê-los em Base64;
- [ ] Escreva tipos de extensão e tags no documento de protocolo e fixe as versões dos decodificadores;
- [ ] Mantenha um caminho de depuração em JSON de texto que possa ser habilitado durante incidentes de produção;
- [ ] Rode um benchmark e registre números reproduzíveis em vez de alegações verbais.
Qual é a diferença entre MessagePack e CBOR?
As codificações são muito parecidas: maps, arrays e strings usam todos um prefixo compacto de tipo mais comprimento. A principal diferença é a padronização. O CBOR é uma RFC da IETF com tipo de mídia na IANA, mecanismo de tags e regras de codificação determinística; o MessagePack é uma especificação da comunidade cujos tipos de extensão dependem de acordos privados. Escolha CBOR quando precisar de assinaturas, consistência entre implementações ou protocolos para dispositivos restritos.
BSON pode substituir o JSON como formato geral?
Não é recomendado. O BSON foi projetado para documentos de banco de dados: todo documento carrega um comprimento total de 4 bytes, todo campo repete seu tipo e nome, e documentos pequenos acabam maiores que o texto JSON. Ele também inclui tipos específicos do MongoDB como ObjectId e MinKey. Fora do MongoDB, a maior parte do seu sistema de tipos fica sem uso enquanto a penalidade de tamanho permanece.
Quanto menor um formato binário torna meu payload?
Depende da forma dos dados. Quando nomes de campo se repetem muito, JSON de texto comprimido e formatos binários ficam próximos; quando os nomes são curtos e os dados estão cheios de caracteres estruturais e números, a vantagem binária é mais clara. Meça amostras reais sem compressão e após gzip, e depois decida, em vez de estimar com um multiplicador genérico.
Ainda consigo depurar dados binários com jq?
Não. jq só processa JSON de texto e vai falhar imediatamente em bytes MessagePack ou CBOR. Você precisa decodificar com a biblioteca correspondente para JSON e então passar para o jq; o CBOR também pode passar por uma ferramenta de notação diagnóstica para virar texto legível. Manter um caminho de depuração em texto é mais barato do que instalar ferramentas sob demanda.