JSON دودویی: انتخاب بین MessagePack، CBOR و BSON
جمعبندی: MessagePack، CBOR و BSON همگی JSON را به بایت فشرده میکنند. هزینه بایت، مدیریت int64 و دودویی را مقایسه کنید و خط روشنی برای ارزش تغییر بگیرید.

جمعبندی: برای تبادل بیرونی و دادهای که افراد باید بازرسی کنند JSON متنی را نگه دارید، و روی پیوندهای داخلی فقط وقتی payloadهای دودویی، عددهای صحیح فراتر از 2^53 یا اعشار دقیق اندازه فشرده یا بیانگری نوع را به مشکل واقعی تبدیل میکنند به MessagePack یا CBOR بروید؛ BSON به اکوسیستم MongoDB تعلق دارد.
JSON متنی فضای سفید را ذخیره میکند؛ قالبهای دودویی نشانهگذاری و شکل متنی عددها را. نام فیلدها همچنان یک بار در هر رکورد ظاهر میشوند، پس صرفهجویی واقعی را پیش از تغییر حساب کنید.
۱. JSON دودویی واقعاً چه چیزی را ذخیره میکند
اول انتظارات را تنظیم کنید. هیچیک از این قالبها «نحو JSON دیگر» نیست. همان مدل داده را در بایت کدگذاری میکنند: اشیا، آرایهها، رشتهها، عددها، بولیها و null. چیزی که حذف میکنند مشخص است:
- نویسههای ساختاری مانند گیومه، دونقطه، ویرگول و آکولاد؛
- شکل متنی اعشاری عددها، پس
32یک بایت میشود؛ - پویش دوباره برای مرزهای رشته، جایگزینشده با پیشوند طول ثابت؛
- کدگذاری Base64 برای payloadهای دودویی، که میتوانند مستقیم بهصورت بایت ذخیره شوند.
چیزی که نمیتوانند حذف کنند به همان اندازه مشخص است: نام فیلدها همچنان بهصورت رشته در هر رکورد ظاهر میشوند. نقشه MessagePack، نقشه CBOR و سند BSON همگی کلید را برای هر ورودی تکرار میکنند. پس وقتی JSON شما ده هزار رکورد با شکل یکسان است، شکاف اندازه فشرده بین متن و دودویی بسیار کوچکتر از شهود است، چون gzip در حذف نام فیلدهای تکراری بهترین است.
۲. این سه قالب از کجا میآیند
| قالب | مشخصات و سال | خاستگاه | استفاده معمول |
|---|---|---|---|
| MessagePack | مشخصات msgpack.org، از ۲۰۰۸ | طراحی مستقل متمرکز بر مینیمالیسم | مقادیر کش، IPC، خطوط لاگ |
| CBOR | RFC 8949 (STD 94)، ۲۰۲۰ | متأثر از MessagePack، استانداردشده توسط IETF | COSE، WebAuthn، CWT، دستگاههای محدود |
| BSON | bsonspec.org، اکوسیستم MongoDB | طراحیشده برای اسناد پایگاهداده | ذخیره و پروتکل سیم MongoDB |
CBOR استانداردشدهترین است: نوع رسانه application/cbor ثبتشده در IANA، قواعد کدگذاری قطعی و CDDL برای توصیف. MessagePack سبکترین است، با مشخصاتی که در یک صفحه جا میشود. BSON سنگینترین است: ObjectId، Decimal128، تاریخها و زیرنوعهای دودویی را در سیستم نوعش میپزد، و همین دلیل محبوبیت واقعیاش فقط در اکوسیستم MongoDB است.
۳. همان داده، چهار کدگذاری
{"name":"joe"} را مثال بگیرید. هزینه بایت آسان قابل مقایسه است:
| کدگذاری | بایت | دنباله بایت |
|---|---|---|
| متن JSON | ۱۴ | 7b 22 6e 61 6d 65 22 3a 22 6a 6f 65 22 7d |
| MessagePack | ۹ | 81 a3 6e 61 6d 65 a3 6a 6f 65 |
| CBOR | ۱۰ | a1 64 6e 61 6d 65 63 6a 6f 65 |
| BSON | ۱۹ | طول کل ۴ بایتی + بایت نوع + name\0 + طول مقدار ۴ بایتی + joe\0 + پایاندهنده |
نه بایت MessagePack را باز کنید:
81: نیبل بالا1000یعنی map، نیبل پایین0001یعنی یک جفت کلید-مقدار؛a3: سه بیت بالای101یعنی رشته با طول ثابت ۳؛6e 61 6d 65: ASCII برایname؛a3: رشته دیگری با طول ۳؛6a 6f 65: همانjoe.
a1 در CBOR نوع اصلی ۵ (map) با طول ۱ است و 64 نوع اصلی ۳ (رشته متنی) با طول ۴، پس معناشناسی تقریباً با MessagePack همریخت است. BSON بزرگتر است: هر سند طول کل ۴ بایتی حمل میکند، هر فیلد یک بایت نوع و نامش را تکرار میکند و هر رشته یک طول بهعلاوه \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
۳.۱ اندازه و سرعت تجزیه: اعداد بازاریابی را نادیده بگیرید
ادعای «۱۰ برابر سریعتر» بودن دودویی را باید به دو پرسش بشکافید.
اندازه. بدون فشردهسازی، دودویی کوچکتر است چون نشانهگذاری و فضای سفید رفته است. اما خطوط لوله واقعی تقریباً همیشه فشرده میکنند: پس از gzip یا brotli، نام فیلدهای تکراری فشرده میشوند و مزیت دودویی بهشدت کوچک میشود. روش فشردهسازی JSON در برابر gzip را دنبال کنید: اول بایت فشرده را اندازه بگیرید، سپس تصمیم بگیرید آیا تغییر قالب ارزشش را دارد.
سرعت تجزیه. دودویی lexing (پویش گیومه، ویرگول و نویسههای عدد) را رد میکند، پس در تئوری باید سریعتر باشد. اما در جاوااسکریپت هزینه غالب تخصیص اشیا و حافظه است که هیچ decoderای نمیتواند از آن دوری کند. شکل داده، تعداد فیلد و کیفیت پیادهسازی همه مهماند، پس تنها نتیجه قابل اعتماد از بنچمارک payloadهای خودتان میآید نه نقل ضریب فروشنده. اگر یک payload برای خواندن یکجا در حافظه خیلی بزرگ است، با رویکرد تجزیه و جریان دادن JSON بزرگ تکهتکهاش کنید و بعد بهره کدگذاری را بسنجید.
۴. فیلدهای int64 و دودویی: دلیل واقعی تغییر
اندازه غالباً دلیل تغییر قالب نیست. بیانگری نوع است.
۴.۱ عددهای صحیح فراتر از 2^53
عددهای JSON در جاوااسکریپت به double مینشینند، پس 9007199254740993 به 9007199254740992 تبدیل میشود. سه قالب دودویی متفاوت برخورد میکنند:
| قالب | عدد صحیح ۶۴ بیتی | ۶۴ بیتی بدون علامت | دقت دلخواه |
|---|---|---|---|
| MessagePack | ✓ int64 (0xd3)، قابل decode به BigInt |
✓ uint64 (0xcf) |
✗ (نوع افزونه لازم دارد) |
| CBOR | ✓ فرمهای ۶۴ بیتی نوع اصلی 0/1 | ✓ همان فضای کدگذاری | ✓ tagهای bignum 2/3 |
| BSON | ✓ int64 (0x12) |
✗ فقط علامتدار | ✗ (Decimal128 ممیز ثابت است، نه عدد صحیح دلخواه) |
CBOR تنها قالبی است که نمایش استاندارد برای عددهای صحیح با دقت دلخواه دارد: مقادیر فراتر از ۶۴ بیت در tag 2 (غیرمنفی) یا tag 3 (منفی) حول یک رشته بایتی پیچیده میشوند. MessagePack و BSON به رشته یا قراردادهای افزونه سفارشی برمیگردند.
۴.۲ payloadهای دودویی
JSON دقیقاً یک راه برای بیان دودویی دارد: Base64، که payload را حدود یک سوم باد میکند و یک کپی decode اضافه میخواهد. MessagePack نوعهای bin دارد (0xc4–0xc6)، CBOR رشتههای بایتی (نوع اصلی ۲) و BSON زیرنوعهای دودویی. وقتی تصویر، هش، بردار یا بلوک فشرده منتقل میکنید، این تفاوت بسیار بیشتر از صرفهجویی چند بایت نشانهگذاری اهمیت دارد.
۵. مقایسه کنار هم
| بُعد | MessagePack | CBOR | BSON |
|---|---|---|---|
| مشخصات | مشخصات جامعه | RFC 8949 در IETF | اکوسیستم MongoDB |
| نوع رسانه | application/msgpack (استاندارد IANA نیست) |
application/cbor (ثبتشده در IANA) |
نوع رسانه عمومی ندارد |
| سیستم نوع | شش نوع JSON بهعلاوه bin و ext | شش نوع JSON بهعلاوه bin، tag و مقدار ساده | گستردهتر: ObjectId، Decimal128، تاریخ، regex، مهر زمانی |
| مکانیسم افزونه | نوعهای افزونه (مثلاً timestamp -1) | Tagها (تاریخ، bignum، CBOR تودرتو) | کدهای نوع ثابت، افزونه با قرارداد اکوسیستم |
| کدگذاری قطعی | الزام استانداردی ندارد | قواعد صریح، قابل استفاده برای امضا | ندارد |
| خودتوصیف | بله، اما بدون schema سطح فیلد | بله، و قابل توصیف با CDDL | بله، با نام فیلدهای تکراری در هر سند |
| سربار سند کوچک | کم | کم | زیاد (پیشوند طول، نام فیلد، پایاندهنده) |
| اکوسیستم | کش، لاگ، برخی RPC | COSE، WebAuthn، IoT | فقط MongoDB |
۶. پشتیبانی مرورگر و Node
Node 24 هیچیک از این codecها را ندارد، پس کتابخانه نصب میکنید. انتخابهای رایج:
| زمان اجرا | 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 decode میکنند و فراتر از بازه امن دقت را از دست میدهند. اگر مقادیر دقیق مهماند، گزینه BigInt را صریح فعال کنید (useBigInt64 در @msgpack/msgpack، کلید معادل در cbor-x)، وگرنه مشکل قدیمی JSON را به قالبی جدید منتقل کردهاید.
۷. قابلیت عیبیابی و سازگاری پروتکل
۷.۱ قابلیت عیبیابی: بزرگترین هزینه پنهان
بزرگترین مزیت JSON متنی اندازه نیست؛ این است که هر زمان میتوانید بازش کنید. پس از روی آوردن به دودویی:
grep،awkوjqهمه از کار میافتند و خطوط لوله لاگ یک گام decode اضافه لازم دارند؛- عیبیابی تولید نیاز به decoder درست در نسخه درست دارد، وگرنه نام فیلدهایی که میبینید اشتباهاند؛
- ترافیک ضبطشده در ابزار توسعه مرورگر بهصورت لکه بایتی ناخوانا ظاهر میشود؛
- diffهای بازبینی کد رشتهای از هگزادسیمال نشان میدهند.
CBOR نماد تشخیصی دارد که بایتها را به متن خوانایی نزدیک به JSON برمیگرداند، مثلاً نقشه را بهصورت {1: 2}؛ MessagePack هم چند decoder خط فرمان دارد. اما همه نیاز به نصب اضافه دارند و بیرون از مسیر عملیات استاندارد مینشینند.
نتیجه: هر زمان افراد باید داده را مکرر بازرسی کنند، هزینه پنهان قالب دودویی از پهنای باندی که ذخیره میکند بیشتر است.
۷.۲ سازگاری پروتکل و افزونهها
- CBOR کاملترین مکانیسم tag را دارد: tag 0 برای رشته تاریخ-زمان، tag 1 برای مهر زمانی Unix، tagهای 2 و 3 برای bignum و tag 24 برای CBOR تودرتو. در ترکیب با کدگذاری قطعی، توسط پروتکلهایی استفاده میشود که به امضا و یکدستی بین پیادهسازیها نیاز دارند، مانند COSE، WebAuthn و CWT.
- MessagePack نوعهای افزونه را به پیادهسازان میسپارد. در عمل تنها نوع رایج timestamp (نوع افزونه -1) است. بین تیمها، هر نوع افزونه باید در سند پروتکل نوشته شود وگرنه طرف مقابل نمیتواند decode کند.
- BSON کدهای نوع ثابت دارد و
ObjectId،Decimal128،MinKeyوMaxKeyمعناشناسی مخصوص MongoDB هستند. برخورد با BSON بهعنوان قالب تبادل عمومی انبوهی نوع استفادهنشده و پیشوند طول استفادهنشده میآورد.
اگر پروتکلی الزام میکند «همان داده باید همان بایتها را تولید کند»، فقط CBOR پاسخ استاندارد میدهد؛ MessagePack و BSON هر دو توافق اضافه بر ترتیب فیلد و جزئیات کدگذاری لازم دارند.
۸. چه زمانی ارزش تغییر دارد
نشانههایی که تغییر را توجیه میکنند:
- payloadها بزرگاند، هر دو سر قابل تغییرند و اندازههای فشرده همچنان تفاوت معنادار دارند؛
- محتوای دودویی منتقل میکنید و باد کردن Base64 و هزینه decode قابل اندازهگیری است؛
- به عددهای صحیح ۶۴ بیتی یا اعشار دقیقی نیاز دارید که doubleهای JSON نمیتوانند نمایش دهند؛
- پیوند سرور-به-سرور است و ابزار عیبیابی میتواند استاندارد شود.
نشانههایی که علیه آناند:
- gzip یا brotli از قبل برپاست و بهره یک درصد تکرقمی است؛
- API عمومی است و شخص ثالثها فقط کلاینتهای HTTP عمومی دارند؛
- داده لاگ یا پیکربندی است که افراد دستی میخوانند؛
- تیم بودجهای برای نگهداری decoderها و سازگاری نسخه ندارد.
مسیر میانی هم کار میکند: JSON را بیرونی نگه دارید و MessagePack را برای کشهای داخلی استفاده کنید. مقادیر کش فقط توسط سرویسهای خودتان خوانده میشوند، پس هم اندازه و هم هزینه تجزیه بهبود مییابد و پاک کردن کش هر مشکلی را بدون دست زدن به قرارداد حل میکند.
۹. چکلیست
- [ ] تعداد بایت فشرده از داده واقعی را مقایسه کنید، نه شمارش بدون فشردهسازی؛
- [ ] سیاست عدد صحیح بزرگ را تصمیم بگیرید: گزینه BigInt، انتقال رشته یا bignumهای CBOR؛
- [ ] فیلدهای دودویی را بهصورت bin یا رشته بایتی بفرستید نه پیچیدن در Base64؛
- [ ] نوعهای افزونه و tagها را در سند پروتکل بنویسید و نسخه decoder را سنجاق کنید؛
- [ ] یک مسیر عیبیابی JSON متنی نگه دارید که در حوادث تولید قابل فعالسازی باشد؛
- [ ] بنچمارک اجرا کنید و اعداد قابل بازتولید ثبت کنید نه ادعای شفاهی.
تفاوت MessagePack و CBOR چیست؟
کدگذاریشان بسیار مشابه است: نقشهها، آرایهها و رشتهها همه از پیشوند فشرده نوع-بهعلاوه-طول استفاده میکنند. تفاوت اصلی استانداردسازی است. CBOR یک RFC در IETF با نوع رسانه IANA، مکانیسم tag و قواعد کدگذاری قطعی است؛ MessagePack مشخصات جامعهای است که نوعهای افزونهاش به توافقهای خصوصی تکیه دارند. وقتی به امضا، یکدستی بین پیادهسازیها یا پروتکلهای دستگاه محدود نیاز دارید CBOR را انتخاب کنید.
آیا BSON میتواند جای JSON را بهعنوان قالب عمومی بگیرد؟
توصیه نمیشود. BSON برای اسناد پایگاهداده طراحی شد: هر سند طول کل ۴ بایتی حمل میکند، هر فیلد نوع و نامش را تکرار میکند و اسناد کوچک بزرگتر از متن JSON میشوند. همچنین نوعهای مخصوص MongoDB مانند ObjectId و MinKey را شامل میشود. بیرون از MongoDB، بیشتر سیستم نوعش استفادهنشده است در حالی که جریمه اندازه میماند.
یک قالب دودویی payload مرا چقدر کوچکتر میکند؟
به شکل داده بستگی دارد. وقتی نام فیلدها بهشدت تکرار میشوند، JSON متنی فشرده و قالبهای دودویی نزدیک میشوند؛ وقتی نامها کوتاهاند و داده پر از نویسههای ساختاری و عددهاست، مزیت دودویی واضحتر است. نمونههای واقعی را هم بدون فشردهسازی و هم پس از gzip بسنجید، سپس تصمیم بگیرید، نه با ضریب عمومی تخمین بزنید.
آیا همچنان میتوانم داده دودویی را با jq عیبیابی کنم؟
نه. jq فقط JSON متنی پردازش میکند و روی بایتهای MessagePack یا CBOR فوراً شکست میخورد. باید اول با کتابخانه منطبق به JSON decode کنید و سپس به jq بدهید؛ CBOR هم میتواند از ابزار نماد تشخیصی عبور کند تا متن خوانا شود. نگه داشتن یک مسیر عیبیابی متنی ارزانتر از نصب ابزار بهصورت موردی است.