Двійковий 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 найбільш стандартизований: медіатип application/cbor, зареєстрований в IANA, детерміновані правила кодування й 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 — це основний тип 5 (мапа) з довжиною 1, а 64 — основний тип 3 (текстовий рядок) з довжиною 4, тож семантика майже ізоморфна MessagePack. BSON більший: кожен документ несе 4-байтову загальну довжину, кожне поле повторює байт типу й свою назву, а кожен рядок повторює довжину плюс завершальний \0. На маленьких документах BSON легко може бути більшим за текст JSON.
Ви можете перевірити префікси безпосередньо, відобразивши ці байти як шістнадцяткові:
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 і двійкові поля: справжня причина перемикатися
Розмір часто не є причиною змінювати формат. Нею є виразність типів.
4.1 Цілі числа понад 2^53
Числа JSON у JavaScript потрапляють у double, тож 9007199254740993 стає 9007199254740992. Три двійкових формати обробляють це по-різному:
| Формат | 64-бітові цілі | Беззнакові 64-бітові | Довільна точність |
|---|---|---|---|
| MessagePack | ✓ int64 (0xd3), декодується в BigInt |
✓ uint64 (0xcf) |
✗ (потрібен тип розширення) |
| CBOR | ✓ 64-бітові форми основного типу 0/1 | ✓ Той самий простір кодування | ✓ теги bignum 2/3 |
| BSON | ✓ int64 (0x12) |
✗ Лише знакові | ✗ (Decimal128 — фіксована точка, не довільне ціле) |
CBOR — єдиний зі стандартним поданням довільної точності цілих: значення понад 64 біти обгортаються тегом 2 (невід'ємні) або тегом 3 (від'ємні) довкола байтового рядка. MessagePack і BSON відкочуються до рядків або власних конвенцій розширення.
4.2 Двійкові навантаження
У JSON є рівно один спосіб виразити двійкові дані: Base64, що роздуває навантаження приблизно на третину й вимагає додаткової копії для декодування. MessagePack має типи bin (0xc4–0xc6), CBOR має байтові рядки (основний тип 2), а BSON має двійкові підтипи. Коли ви передаєте зображення, хеші, вектори чи стиснені блоки, ця різниця важить набагато більше за економію кількох байтів пунктуації.
5. Порівняння пліч-о-пліч
| Вимір | MessagePack | CBOR | BSON |
|---|---|---|---|
| Специфікація | Специфікація спільноти | IETF RFC 8949 | Екосистема MongoDB |
| Медіатип | application/msgpack (не стандарт IANA) |
application/cbor (зареєстрований в IANA) |
Загального медіатипу немає |
| Система типів | Шість типів JSON плюс bin і ext | Шість типів JSON плюс bin, теги й прості значення | Ширша: ObjectId, Decimal128, дати, regex, часові мітки |
| Механізм розширення | Типи розширення (наприклад, часова мітка -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усі перестають працювати, а конвеєри логів потребують додаткового кроку декодування;- Налагодження у виробництві потребує правильного декодера правильної версії, інакше побачені назви полів будуть неправильними;
- Захоплений трафік показується в інструментах розробника браузера як нечитабельний байтовий блок;
- Diff під час рев'ю коду показує рядок шістнадцяткових цифр.
CBOR має діагностичну нотацію, яка відображає байти назад у читабельний текст, близький до JSON, наприклад мапу як {1: 2}; MessagePack теж має кілька декодерів командного рядка. Але всі вони вимагають додаткового встановлення й лежать поза стандартним шляхом операцій.
Висновок: щоразу, коли людям треба часто оглядати дані, прихована ціна двійкового формату перевищує заощаджену пропускну здатність.
7.2 Сумісність протоколів і розширення
- CBOR має найповніший механізм тегів: тег 0 для рядка дати-часу, тег 1 для мітки часу Unix, теги 2 і 3 для bignums, і тег 24 для вбудованого CBOR. У поєднанні з детермінованим кодуванням його використовують протоколи, що потребують підписів і міжреалізаційної узгодженості, як-от COSE, WebAuthn і CWT.
- MessagePack залишає типи розширення реалізаторам. На практиці єдиний поширений — часова мітка (тип розширення -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 також може пройти через інструмент діагностичної нотації й стати читабельним текстом. Тримати текстовий шлях налагодження дешевше, ніж встановлювати інструментарій на вимогу.