Двоичный JSON: выбор между MessagePack, CBOR и BSON
Главное: MessagePack, CBOR и BSON сжимают JSON в байты. Сравните байтовую стоимость, обработку int64 и двоичных данных и получите чёткую границу, когда переключение того стоит.

Главное: держите текстовый JSON для внешнего обмена и для данных, которые людям нужно просматривать, и переходите на MessagePack или CBOR на внутренних каналах только когда двоичные полезные нагрузки, целые за пределами 2^53 или точные десятичные дроби делают сжатый размер или выразительность типов реальной проблемой; BSON принадлежит экосистеме MongoDB.
Текстовый JSON экономит пробелы; двоичные форматы экономят пунктуацию и текстовую форму чисел. Имена полей всё равно появляются один раз на запись, поэтому вычислите реальную экономию, прежде чем переключаться.
1. Что двоичный JSON реально экономит
Сначала установите ожидания. Ни один из этих форматов не является «другим синтаксисом JSON». Они кодируют одну и ту же модель данных в байтах: объекты, массивы, строки, числа, булевы и null. Что они удаляют, конкретно:
- Структурные символы, такие как кавычки, двоеточия, запятые и скобки;
- Десятичную текстовую форму чисел, так что
32становится одним байтом; - Повторное сканирование границ строк, заменённое префиксом фиксированной длины;
- Кодирование Base64 для двоичных полезных нагрузок, которые можно хранить как байты напрямую.
Что они не могут удалить, столь же конкретно: имена полей всё равно появляются как строки в каждой записи. Карта MessagePack, карта CBOR и документ BSON все повторяют ключ для каждой записи. Поэтому когда ваш JSON — десять тысяч записей одной формы, разрыв в сжатом размере между текстом и двоичным форматом гораздо меньше, чем подсказывает интуиция, потому что gzip лучше всего устраняет повторяющиеся имена полей.
2. Откуда берутся три формата
| Формат | Спецификация и год | Происхождение | Типичное применение |
|---|---|---|---|
| MessagePack | Спецификация msgpack.org, с 2008 | Независимый дизайн, сфокусированный на минимализме | Значения кеша, IPC, конвейеры логов |
| CBOR | RFC 8949 (STD 94), 2020 | Под влиянием MessagePack, стандартизирован IETF | COSE, WebAuthn, CWT, ограниченные устройства |
| BSON | bsonspec.org, экосистема MongoDB | Разработан для документов баз данных | Хранение и проводной протокол MongoDB |
CBOR наиболее стандартизирован: зарегистрированный в IANA медиатип application/cbor, детерминированные правила кодирования и CDDL для описания. MessagePack самый лёгкий, со спецификацией на одну страницу. BSON самый тяжёлый: он встраивает ObjectId, Decimal128, даты и двоичные подтипы в свою систему типов, поэтому по-настоящему популярен только внутри экосистемы MongoDB.
3. Одни данные, четыре кодировки
Возьмём {"name":"joe"} в качестве примера. Байтовую стоимость легко сравнивать:
| Кодировка | Байты | Последовательность байтов |
|---|---|---|
| Текст 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 | 4-байтовая общая длина + байт типа + name\0 + 4-байтовая длина значения + joe\0 + терминатор |
Разберём девять байтов MessagePack:
81: старший ниббл1000означает карту, младший ниббл0001означает одну пару ключ-значение;a3: старшие три бита101означают строку фиксированной длины 3;6e 61 6d 65: ASCII дляname;a3: ещё одна строка длины 3;6a 6f 65:joe.
a1 в CBOR — major type 5 (карта) с длиной 1, а 64 — major type 3 (текстовая строка) с длиной 4, поэтому семантика почти изоморфна MessagePack. BSON больше: каждый документ несёт 4-байтовую общую длину, каждое поле повторяет байт типа и своё имя, а каждая строка повторяет длину плюс завершающий \0. На небольших документах BSON легко бывает больше текста JSON.
Вы можете проверить префиксы напрямую, отрендерив эти байты в hex:
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 Размер и скорость парсинга: игнорируйте маркетинговые цифры
Утверждения, что двоичный формат «в 10 раз быстрее», следует разделить на два вопроса.
Размер. Без сжатия двоичный меньше, потому что пунктуация и пробелы исчезли. Но реальные конвейеры почти всегда сжимают: после gzip или brotli повторяющиеся имена полей сжимаются, и двоичное преимущество резко уменьшается. Следуйте методу в Минификация JSON против gzip: сначала измерьте сжатые байты, затем решайте, стоит ли смена формата.
Скорость парсинга. Двоичный формат пропускает лексинг (сканирование кавычек, запятых и символов чисел), поэтому теоретически должен быть быстрее. Однако в JavaScript доминирующая стоимость — выделение объектов и памяти, чего не может избежать ни один декодер. Форма данных, число полей и качество реализации — всё имеет значение, поэтому единственный надёжный вывод приходит из бенчмаркинга ваших собственных полезных нагрузок, а не из цитирования множителя поставщика. Если одна полезная нагрузка слишком велика, чтобы читать её в память сразу, нарежьте её подходом, описанным в парсинг и потоковая обработка большого JSON, прежде чем измерять выгоду кодировки.
4. Поля int64 и binary: реальная причина переключения
Размер часто не является причиной смены форматов. Выразительность типов является.
4.1 Целые за пределами 2^53
Числа JSON в JavaScript попадают в double, поэтому 9007199254740993 становится 9007199254740992. Три двоичных формата обрабатывают это по-разному:
| Формат | 64-битные целые | Беззнаковые 64 бита | Произвольная точность |
|---|---|---|---|
| MessagePack | ✓ int64 (0xd3), декодируется в BigInt |
✓ uint64 (0xcf) |
✗ (нужен тип расширения) |
| CBOR | ✓ 64-битные формы major type 0/1 | ✓ То же пространство кодирования | ✓ теги bignum 2/3 |
| BSON | ✓ int64 (0x12) |
✗ Только знаковые | ✗ (Decimal128 — фиксированная точка, а не произвольное целое) |
CBOR — единственный со стандартным представлением для целых с произвольной точностью: значения за пределами 64 бит оборачиваются в тег 2 (неотрицательные) или тег 3 (отрицательные) вокруг байтовой строки. MessagePack и BSON откатываются к строкам или самодельным соглашениям расширений.
4.2 Двоичные полезные нагрузки
У JSON ровно один способ выразить двоичные данные: Base64, который раздувает полезную нагрузку примерно на треть и требует дополнительной копии при декодировании. У MessagePack есть типы bin (0xc4–0xc6), у CBOR — байтовые строки (major type 2), у BSON — двоичные подтипы. Когда вы транспортируете изображения, хеши, векторы или сжатые блоки, эта разница важнее, чем экономия нескольких байтов пунктуации.
5. Сравнение бок о бок
| Аспект | MessagePack | CBOR | BSON |
|---|---|---|---|
| Спецификация | Спецификация сообщества | IETF RFC 8949 | Экосистема MongoDB |
| Медиатип | application/msgpack (не IANA-стандарт) |
application/cbor (зарегистрирован IANA) |
Нет общего медиатипа |
| Система типов | Шесть типов JSON плюс bin и ext | Шесть типов JSON плюс bin, теги и простые значения | Шире: ObjectId, Decimal128, даты, regex, метки времени |
| Механизм расширения | Типы расширения (например, timestamp -1) | Теги (даты, bignums, встроенный CBOR) | Фиксированные коды типов, расширения по соглашению экосистемы |
| Детерминированная кодировка | Нет стандартного требования | Явные правила, пригодные для подписей | Нет |
| Самоописывающий | Да, но без схемы уровня полей | Да, и описывается через CDDL | Да, с повторением имён полей в каждом документе |
| Накладные расходы на малые документы | Низкие | Низкие | Высокие (префикс длины, имена полей, терминатор) |
| Экосистема | Кеши, логи, некоторые RPC | COSE, WebAuthn, IoT | Только MongoDB |
6. Поддержка в браузере и Node
Node 24 не содержит ни одного из этих кодеков, поэтому вы устанавливаете библиотеку. Частые варианты:
| Среда выполнения | MessagePack | CBOR | BSON |
|---|---|---|---|
| Node.js | @msgpack/msgpack, msgpackr |
cbor-x, borc |
bson |
| Браузер | @msgpack/msgpack |
браузерная сборка cbor-x, cbor-web |
bson |
Оба семейства работают на Uint8Array и не требуют Buffer. Следите за различиями версий:
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 };
}
Большинство библиотек по умолчанию декодируют большие целые в number и теряют точность за пределами безопасного диапазона. Если точные значения важны, включите опцию BigInt явно (useBigInt64 в @msgpack/msgpack, эквивалентный переключатель в cbor-x), иначе вы просто перенесли старую проблему JSON в новый формат.
7. Отлаживаемость и совместимость протокола
7.1 Отлаживаемость: самая большая скрытая цена
Самое большое преимущество текстового JSON — не размер; это то, что вы можете открыть его в любой момент. После перехода на двоичный формат:
grep,awkиjqперестают работать, а конвейерам логов нужен дополнительный шаг декодирования;- Отладка продакшена требует правильного декодера правильной версии, иначе имена полей, которые вы видите, будут неверными;
- Захваченный трафик показывается в инструментах разработчика браузера как нечитаемый байтовый блоб;
- Различия при ревью кода показывают строку шестнадцатеричных символов.
У CBOR есть диагностическая нотация, которая рендерит байты обратно в читаемый текст, близкий к JSON, например карту как {1: 2}; у MessagePack тоже есть несколько декодеров командной строки. Но все они требуют дополнительной установки и находятся вне стандартного операционного пути.
Вывод: всякий раз, когда людям нужно часто просматривать данные, скрытая цена двоичного формата превышает экономию пропускной способности.
7.2 Совместимость протокола и расширения
- CBOR имеет самый полный механизм тегов: тег 0 для строки дата-время, тег 1 для Unix-метки времени, теги 2 и 3 для bignums и тег 24 для встроенного CBOR. В сочетании с детерминированной кодировкой его используют протоколы, которым нужны подписи и кросс-реализационная согласованность, такие как COSE, WebAuthn и CWT.
- MessagePack оставляет типы расширений реализаторам. На практике единственный распространённый — timestamp (тип расширения -1). Между командами каждый тип расширения должен быть записан в документ протокола, иначе другая сторона не сможет его декодировать.
- BSON имеет фиксированные коды типов, а
ObjectId,Decimal128,MinKeyиMaxKey— специфическая семантика MongoDB. Рассматривать BSON как общий формат обмена означает кучу неиспользуемых типов и неиспользуемых префиксов длины.
Если протокол требует «одни и те же данные должны давать одни и те же байты», только CBOR даёт стандартный ответ; MessagePack и BSON оба нуждаются в дополнительном соглашении о порядке полей и деталях кодирования.
8. Когда стоит переключаться
Сигналы, оправдывающие переключение:
- Полезные нагрузки велики, оба конца можно менять, и сжатые размеры всё ещё различаются значимо;
- Вы транспортируете двоичный контент, и раздувание Base64 и стоимость декодирования измеримы;
- Вам нужны 64-битные целые или точные десятичные дроби, которые double в JSON не может представить;
- Канал сервер-сервер, и диагностический инструментарий можно стандартизировать.
Сигналы, говорящие против:
- gzip или brotli уже на месте, а выгода — однозначный процент;
- API публичный, и у третьих сторон только универсальные HTTP-клиенты;
- Данные — логи или конфигурация, которые люди читают вручную;
- У команды нет бюджета на поддержание декодеров и совместимости версий.
Работает и средний путь: держите JSON снаружи и используйте MessagePack для внутренних кешей. Значения кеша читаются только вашими собственными сервисами, поэтому и размер, и стоимость парсинга улучшаются, а очистка кеша решает любую проблему без изменения контракта.
9. Контрольный список
- [ ] Сравните сжатые количества байтов из реальных данных, а не несжатые;
- [ ] Решите политику больших целых: опция BigInt, строковый транспорт или bignums CBOR;
- [ ] Отправляйте двоичные поля как bin или байтовые строки вместо оборачивания в Base64;
- [ ] Запишите типы расширений и теги в документ протокола и закрепите версии декодеров;
- [ ] Держите путь отладки текстового JSON, который можно включить во время производственных инцидентов;
- [ ] Запустите бенчмарк и запишите воспроизводимые числа вместо словесных заявлений.
В чём разница между MessagePack и CBOR?
Их кодировки очень похожи: карты, массивы и строки используют компактный префикс тип-плюс-длина. Главная разница — стандартизация. CBOR — IETF RFC с медиатипом IANA, механизмом тегов и детерминированными правилами кодирования; MessagePack — спецификация сообщества, чьи типы расширений зависят от частных соглашений. Выбирайте CBOR, когда нужны подписи, кросс-реализационная согласованность или протоколы для ограниченных устройств.
Может ли BSON заменить JSON как общий формат?
Не рекомендуется. BSON разработан для документов баз данных: каждый документ несёт 4-байтовую общую длину, каждое поле повторяет свой тип и имя, а небольшие документы в итоге больше текста JSON. Он также включает специфические типы MongoDB, такие как ObjectId и MinKey. За пределами MongoDB большая часть его системы типов не используется, а размерный штраф остаётся.
Насколько меньше двоичный формат делает мою полезную нагрузку?
Зависит от формы данных. Когда имена полей сильно повторяются, сжатый текстовый JSON и двоичные форматы оказываются близки; когда имена коротки, а данные полны структурных символов и чисел, двоичное преимущество яснее. Измерьте реальные образцы и без сжатия, и после gzip, затем решайте, а не оценивайте универсальным множителем.
Можно ли по-прежнему отлаживать двоичные данные через jq?
Нет. jq обрабатывает только текстовый JSON и сразу падает на байтах MessagePack или CBOR. Вы должны сначала декодировать подходящей библиотекой в JSON, а затем передать в jq; CBOR также может пройти через инструмент диагностической нотации, чтобы стать читаемым текстом. Держать текстовый путь отладки дешевле, чем устанавливать инструментарий по требованию.