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 الحجم وسرعة التحليل: تجاهل أرقام التسويق
يجب تقسيم ادعاءات أن الثنائي "أسرع بعشر مرات" إلى سؤالين.
الحجم. دون ضغط، يكون الثنائي أصغر لأن علامات الترقيم والمسافات اختفت. لكن خطوط الأنابيب الحقيقية تضغط دائمًا تقريبًا: بعد gzip أو brotli تُضغط أسماء الحقول المتكررة، فيتقلص تفوق الثنائي تقلصًا هائلًا. اتبع الطريقة في تصغير JSON مقابل gzip: قس البايتات المضغوطة أولًا، ثم قرر هل يستحق تغيير الصيغة.
سرعة التحليل. يتخطى الثنائي التحليل المعجمي (مسح علامات الاقتباس والفواصل ومحارف الأعداد)، فينبغي أن يكون أسرع نظريًا. لكن في JavaScript، الكلفة المهيمنة هي تخصيص الكائنات والذاكرة، وهي ما لا يستطيع أي مفكك ترميز تجنبه. شكل البيانات وعدد الحقول وجودة التنفيذ كلها مهمة، فالاستنتاج الموثوق الوحيد يأتي من قياس حمولاتك بنفسك بدلًا من اقتباس مضاعف من مورّد. إن كانت حمولة واحدة كبيرة أكثر من أن تُقرأ في الذاكرة دفعة واحدة، فقطّعها بالأسلوب الموصوف في تحليل JSON الكبير وبثه قبل قياس مكسب الترميز.
4. int64 والحقول الثنائية: السبب الحقيقي للتبديل
غالبًا ما لا يكون الحجم سبب تغيير الصيغ. بل قدرة التعبير عن الأنواع.
4.1 الأعداد الصحيحة التي تتجاوز 2^53
تهبط أعداد JSON في double في JavaScript، فيصبح 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 والتواريخ والتعبيرات النمطية والطوابع الزمنية |
| آلية الامتداد | أنواع امتداد (مثل timestamp -1) | وسوم (التواريخ وbignum وCBOR المضمّن) | رموز أنواع ثابتة، وامتدادات وفق عرف المنظومة |
| ترميز حتمي | لا متطلب قياسي | قواعد صريحة تصلح للتوقيعات | لا شيء |
| واصف لنفسه | نعم، لكن دون مخطط على مستوى الحقل | نعم، وقابل للوصف بـ CDDL | نعم، مع تكرار أسماء الحقول لكل مستند |
| عبء المستند الصغير | منخفض | منخفض | مرتفع (بادئة الطول وأسماء الحقول والمُنهي) |
| المنظومة | ذاكرات وسجلات وبعض RPC | COSE وWebAuthn وإنترنت الأشياء | 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';
/**
* يرمّز كائنًا إلى بايتات MessagePack ثم يفك ترميزه فورًا للتحقق.
* @param payload الكائن العادي المطلوب ترميزه
* @returns القيمة المفكوكة وطول البايتات المرموزة
*/
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 لـ bignum، والوسم 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 أو نقل كنص أو bignum في CBOR؛
- [ ] أرسل الحقول الثنائية كـ bin أو سلاسل بايتات بدلًا من تغليفها بـ Base64؛
- [ ] اكتب أنواع الامتداد والوسوم في وثيقة البروتوكول وثبّت إصدارات المفككات؛
- [ ] أبقِ مسار تصحيح بـ JSON النصي يمكن تفعيله أثناء حوادث الإنتاج؛
- [ ] شغّل قياسًا وسجّل أرقامًا قابلة للتكرار بدلًا من ادعاءات كلامية.
ما الفرق بين MessagePack وCBOR؟
ترميزهما متشابه جدًا: الخرائط والمصفوفات والنصوص كلها تستخدم بادئة نوع وطول مضغوطة. الفرق الرئيسي هو المعيارية. CBOR هو RFC من IETF بنوع وسائط من IANA وآلية وسوم وقواعد ترميز حتمية؛ أما MessagePack فمواصفة مجتمعية تعتمد أنواع امتدادها على اتفاقات خاصة. اختر CBOR عندما تحتاج إلى توقيعات أو اتساق بين التنفيذات أو بروتوكولات أجهزة مقيّدة.
هل يمكن لـ BSON أن يحل محل JSON كصيغة عامة؟
لا يُنصح بذلك. صُمم BSON لمستندات قواعد البيانات: كل مستند يحمل طولًا كليًا بـ4 بايتات، وكل حقل يكرر نوعه واسمه، فينتهي المستندات الصغيرة أكبر من JSON النصي. كما يتضمن أنواعًا خاصة بـ MongoDB مثل ObjectId وMinKey. خارج MongoDB، يبقى معظم نظام أنواعه غير مستخدم بينما تظل غرامة الحجم قائمة.
إلى أي حد تصغّر الصيغة الثنائية حمولتي؟
يعتمد على شكل البيانات. عندما تتكرر أسماء الحقول بكثافة، يتقارب JSON النصي المضغوط والصيغ الثنائية؛ وعندما تكون الأسماء قصيرة والبيانات مليئة بمحارف البنية والأعداد، يصبح تفوق الثنائي أوضح. قس عينات حقيقية غير مضغوطة وبعد gzip، ثم قرر، بدلًا من التقدير بمضاعف عام.
هل ما زال يمكنني تصحيح البيانات الثنائية بـ jq؟
لا. يعالج jq JSON النصي فقط وسيفشل فورًا على بايتات MessagePack أو CBOR. عليك فك الترميز بالمكتبة المطابقة إلى JSON أولًا ثم تمريره إلى jq؛ كما يمكن تمرير CBOR عبر أداة تدوين تشخيصي ليصبح نصًا مقروءًا. إبقاء مسار تصحيح نصي أرخص من تثبيت الأدوات عند الحاجة.