JSON Toolkit

قراءة حدود صياغة JSON من RFC 8259

الخلاصة: ترجمة إنتاجات صياغة RFC 8259 إلى لغة واضحة: أي الأحرف يجوز أن تظهر مباشرة، وأيها يجب هروبها، وأين تترك المواصفة مساحة عن قصد.

مبتدئقراءة نحو 11 دقيقةنُشر في 2026-10-30حُدِّث في 2026-10-30
غلاف قراءة حدود صياغة JSON من RFC 8259

الخلاصة: عندما يظهر خطأ نحوي في JSON، ابحث عن أول حرف غير صالح وصنّفه كعلامة اقتباس مفردة أو فاصلة زائدة أو تعليق أو حرف تحكم بدلًا من إعادة قراءة المستند كله؛ ومن جهة الترميز، تأكد من UTF-8 دون BOM واحمل الأعداد الصحيحة فوق 2^53 - 1 كنصوص.

لا يتجاوز متن المواصفة اثني عشر صفحة، لكن خلف كل "MUST" و"SHOULD" و"MAY" يكمن حادث أبقى فريقًا مستيقظًا طوال الليل مرة.

1. كيف تتصل النصوص الثلاثة

غالبًا ما يتشوش تعلّم JSON بسبب ثلاثة أرقام: RFC 4627 وRFC 7159 وRFC 8259، إضافةً إلى ECMA-404. والعلاقة في الواقع واضحة تمامًا:

النص التاريخ الحالة الفرق الجوهري
RFC 4627 2006 متجاوَز يجب أن يكون المستوى الأعلى كائنًا أو مصفوفة
RFC 7159 2014 متجاوَز خُفِّف المستوى الأعلى ليصبح أي قيمة JSON
RFC 8259 2017.12 المعيار الحالي UTF-8 صريح، وإضافة إرشادات التشغيل البيني
ECMA-404 2013، الطبعة الثانية 2017.12 مواصفة صياغة تعرّف الصياغة فقط، ولا تغطي الترميز أو النقل

أيها تقرأ؟ RFC 8259 وحدها كافية. وECMA-404 مكافئة في جزء الصياغة؛ والفرق أن RFC 8259 تحدد أيضًا "النقل بوصفه UTF-8" و"مشكلات التشغيل البيني التي يجب أن يراقبها مطورو التطبيقات".

2. ستة أنواع قيم

يمكن تكثيف إنتاجات صياغة المواصفة في مخطط واحد:

JSON-text  = ws value ws
value      = object / array / number / string / "true" / "false" / "null"
object     = "{" [ member *( "," member ) ] "}"
member     = string ws ":" ws value
array      = "[" [ value *( "," value ) ] "]"

لا توجد سوى ست نقاط أساسية:

  1. الكائنات تستخدم الأقواس المعقوفة، وتُفصل الأعضاء بفواصل، والفاصلة بعد العضو الأخير غير مسموح بها؛
  2. المصفوفات تستخدم الأقواس المربعة، وتُفصل العناصر بفواصل، والفواصل الزائدة غير مسموح بها كذلك؛
  3. الأعداد لا تسمح بـ + بادئة، ولا أصفار بادئة (01 غير صالح)، ولا نقطة عشرية مجردة (1. غير صالح)، ولا NaN أو Infinity؛
  4. النصوص يجب أن تستخدم علامات الاقتباس المزدوجة؛ وعلامات الاقتباس المفردة غير صالحة؛
  5. القيم المنطقية وnull حرفيات بحروف صغيرة؛ وTrue وNULL كلاهما غير صالح؛
  6. أحرف المسافات البيضاء فقط 0x20 (مسافة)، و0x09 (tab)، و0x0A (تغذية سطر)، و0x0D (عودة عربة) يجوز أن تظهر بين العناصر البنيوية.

المقطع التالي JSON صالح، رغم أن كثيرين يفترضون خطأه أول مرة يرونه — لأنه نص مجرد على المستوى الأعلى:

"just a string"

منذ RFC 7159 يمكن أن يكون المستوى الأعلى أي قيمة فعلًا. لكن في API لا يزال يُستحب استخدام الكائنات أو المصفوفات فقط؛ والسبب في القسم 6.

3. ما الذي يجوز أن يظهر فعليًا داخل نص

هذا هو الجزء الأكثر عرضة للخداع بعبارة "يعمل جيدًا على جهازي". وقواعد المواصفة للنصوص هي:

  • الأحرف المسموح بظهورها مباشرة: %x20-21، %x23-5B، %x5D-10FFFF؛
  • بعبارة أخرى، علامة الاقتباس المزدوجة " (0x22) والشرطة المائلة العكسية \ (0x5C) يجب هروبهما؛
  • كل أحرف التحكم دون 0x20 يجب هروبها، بما في ذلك تغذية السطر وtab وعودة العربة؛
  • تسلسلات الهروب المتاحة فقط هي \" و\\ و\/ و\b و\f و\n و\r و\t و\uXXXX.
{
  "ok": "Chinese text, emoji 🎉 and full-width symbols can be written directly",
  "escaped": "A line break goes here\nand a tab\tfinishes it",
  "control": "\u0007 is the bell character and must be escaped",
  "quote": "He said: \"don't use single quotes\""
}

\/ هروب قديم (يمنع </script> من الإغلاق المبكر). ونادرًا ما تحتاج إليه الحالات الحديثة، لكن المحللات يجب أن تدعمه.

4. الترميز: UTF-8 دون BOM

يقول الفصل 8.1 من RFC 8259 ثلاثة أمور:

  1. نص JSON المستخدم للتبادل يجب أن يكون مُرمَّزًا بـ UTF-8؛
  2. يجب أن لا تضيف التطبيقات BOM (EF BB BF) في بداية النص؛
  3. يجوز للمحللات تجاهل BOM لتكون أكثر متانة.

فشلان نموذجيًا في الواقع:

  • Windows Notepad يحفظ بصيغة "UTF-8 with BOM"، فيرفض JSON.parse الملف بالرسالة Unexpected token؛
  • ملف JSON صيني محفوظ بترميز GBK يتحول إلى نص مشوّه في مسار يفك بوصفه UTF-8 فقط، ومع ذلك تمر الصياغة صدفةً، فيُؤجَّل الخطأ إلى طبقة الأعمال.

المعالجة بسيطة: انزع BOM عند قراءة الملف، ثم فك الترميز صراحةً بـ UTF-8؛ وعند الكتابة تأكد من عدم إضافة BOM.

/**
 * Removes the UTF-8 BOM at the start of a string.
 * @param text The original text
 * @returns The text without a BOM
 */
function stripBom(text: string): string {
  return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
}

وملاحظة عن أنواع الوسائط هنا: لم يعد application/json يقبل معامل charset؛ فكتابته لا تثير خطأً، لكنها ممارسة قديمة.

5. ثلاثة مواضع تترك فيها المواصفة مساحة عن قصد

المواضع التي تمتنع فيها المواصفة عن التحديد هي بالضبط مصدر اختلافات التطبيقات.

1. نطاق الأعداد ودقتها

لا تقول RFC 8259 إلا "هذه المواصفة لا تحد نطاق الأعداد ولا دقتها"، مع الإشارة إلى أن معظم التطبيقات تستخدم دقة الفاصلة العائمة المزدوجة IEEE 754. وهذا يعني أن 9223372036854775807 نفسه يصبح 9223372036854776000 في JavaScript، بينما يبقى دقيقًا في long في Java.

الخلاصة: الأعداد الصحيحة فوق 2^53 - 1، والمبالغ النقدية التي تحتاج دقة عشرية كثيرة، يجب أن تُحمَل دائمًا كنصوص، وأن يُكتب pattern صراحةً في المخطط.

2. المفاتيح المكررة

صياغة المواصفة هي "الأسماء داخل كائن ينبغي (SHOULD) أن تكون فريدة"؛ ولا تقول ما يجب فعله إن لم تكن كذلك.

  • يحتفظ JSON.parse بالآخر؛
  • بعض المحللات تحتفظ بالأول؛
  • بعض المكتبات تثير خطأً ببساطة.

الخلاصة: لا تكتب مفاتيح مكررة، ولا تعتمد على أي سلوك محدد في مدققاتك. وإضافة خطوة "كشف المفاتيح المكررة" في وقت البناء أرخص بكثير من التحقيق لاحقًا.

3. حدود العمق والطول

لا تضع المواصفة حدًا لعمق التداخل، لكن لكل تطبيق حد. ويثير V8 الخطأ RangeError: Maximum call stack size exceeded عند عشرات الآلاف من مستويات التداخل. ويجب أن تفرض الخوادم حدودها الخاصة؛ فهذا متطلب أمني لا متطلب مواصفة.

6. اختبر فهمك بأمثلة مضادة

كل سطر أدناه JSON غير صالح. أعطِها لمحلل وانظر هل رسائل الخطأ مفيدة:

{ 'name': 'json' }        // single quotes
{ name: "json" }          // key name not quoted
{"a": 1,}                 // trailing comma
[1, 2, 3,]                // trailing comma in an array
{"a": 01}                 // leading zero
{"a": +1}                 // leading plus sign
{"a": .5}                 // missing integer part
{"a": NaN}                // non-standard literal
{"a": undefined}          // not a JSON value
{"a": 1} // comment       // comment

حالة حدية مثيرة: {"a": 1} متبوعًا بتعليق صالح في JSON5، وصالح في JSONC، وغير صالح في JSON القياسي. لذا فإن "ملف الإعدادات يعمل جيدًا لكن API ترفضه" مشكلة لهجة تقريبًا دائمًا.

7. قائمة تحقق الإنتاج

  • [ ] كل JSON المنقول خارجيًا يستخدم application/json دون charset؛
  • [ ] مسارات قراءة/كتابة الملفات تنزع BOM وتستخدم UTF-8 صريحًا؛
  • [ ] الأعداد الصحيحة فوق 2^53 تُحمَل كنصوص وتُوسم في المخطط؛
  • [ ] تُكشف المفاتيح المكررة في وقت البناء؛
  • [ ] يحد الخادم من حجم جسم الطلب وعمق التداخل؛
  • [ ] القيم على المستوى الأعلى في API العامة كائنات أو مصفوفات، وليست قيمًا مجردة أبدًا.

FAQ

كيف أعرف بسرعة إن كان نص ما JSON صالحًا؟

أعطِ النص لمحلل أو لأداة تحليل على الإنترنت، واقرأ موضع أول حرف غير صالح، ثم قارنه بأنواع القيم الستة في RFC 8259 وقواعد المسافات البيضاء. يجوز أن يكون المستوى الأعلى أي قيمة JSON، لكن بين العناصر البنيوية لا يُسمح إلا بالمسافة وtab وتغذية السطر وعودة العربة. ووجود علامة اقتباس مفردة أو مفتاح غير مقتبس أو فاصلة زائدة أو تعليق يعني أنه ليس JSON قياسيًا.

لماذا يجب أن يستخدم JSON ترميز UTF-8 وأن يحمل بلا BOM؟

يشترط الفصل 8.1 من RFC 8259 أن يكون نص JSON المستخدم للتبادل مُرمَّزًا بـ UTF-8، ويقول إن التطبيقات يجب أن لا تضيف BOM في البداية، ويسمح للمحللات بتجاهل BOM من باب المتانة. والملف الذي يحمل BOM يرفضه JSON.parse بالرسالة Unexpected token، رغم أن لا شيء يبدو خاطئًا للعين. لذا انزع BOM عند القراءة وفك الترميز صراحةً بـ UTF-8.

هل أستخدم JSON5 أو JSONC للالتفاف على حدود الصياغة هذه؟

نعم، لكن للقراءة المحلية فقط مثل ملفات الإعدادات؛ أما البيانات المتبادلة بين الأنظمة فيجب أن تبقى JSON قياسيًا. تُظهر الأمثلة المضادة أن {"a": 1} متبوعًا بتعليق صالح في JSON5 وJSONC وغير صالح في JSON القياسي، ولهذا فإن "ملف الإعدادات يعمل لكن API ترفضه" مشكلة لهجة تقريبًا دائمًا. وعند التحويل، انزع التعليقات وعلامات الاقتباس المفردة والفواصل الزائدة أولًا وسلّم الناتج لمحلل قياسي.