JSON5 وJSONC: JSON مع تعليقات
الخلاصة: وُجدت اللهجتان لأن الإعداد الذي يكتبه البشر يحتاج إلى تعليقات وفواصل زائدة، لكن صياغتهما ومدى تغلغلهما في المنظومة يختلفان، فالاختيار الخاطئ يعطّل سلسلة أدواتك.

الخلاصة: استخدم JSONC للتعليقات داخل إعداد VS Code وTypeScript، واستخدم JSON5 عندما تحتاج إلى صياغة حرفيات ES5.1 الأوسع مثل علامات الاقتباس المفردة أو المفاتيح غير المقتبسة، وأبقِ APIs العامة على JSON القياسي.
لا يسمح JSON القياسي بالتعليقات، فتكتبها كل فرقة بهدوء رغم ذلك — حتى ينكسر البناء يومًا. بدلًا من فعل ذلك سرًا، اختر لهجة مناسبة.
1. للهجتين دافعان مختلفان
- JSONC (JSON مع تعليقات): JSON قياسي مع تعليقات وفواصل زائدة، دون تغييرات صياغة أخرى تقريبًا. إنه المعيار الفعلي في منظومة VS Code — يستخدمه
settings.jsonوtsconfig.jsonو.eslintrc.jsonجميعًا. - JSON5: أكثر طموحًا بكثير، ويتوافق مباشرة مع حرفية الكائن في ES5.1 — علامات اقتباس مفردة، ومفاتيح غير مقتبسة، وست عشري، و
Infinity/NaN، واستمرار السطر بالشرطة المائلة العكسية، وعلامات+بادئة.
جملة واحدة للتمييز بينهما: JSONC يجعل الكتابة مريحة فقط؛ أما JSON5 فيتيح لك كتابة ملف إعداد كأنه JavaScript.
2. جدول مقارنة الصياغة
| الميزة | JSON القياسي | JSONC | JSON5 |
|---|---|---|---|
التعليقات // /* */ |
✗ | ✓ | ✓ |
| الفواصل الزائدة | ✗ | ✓ | ✓ |
| نصوص بعلامات اقتباس مفردة | ✗ | ✗ | ✓ |
| مفاتيح غير مقتبسة | ✗ | ✗ | ✓ |
| أعداد ست عشرية | ✗ | ✗ | ✓ |
نقطة عشرية بادئة/ختامية .5 5. |
✗ | ✗ | ✓ |
أعداد تبدأ بـ + |
✗ | ✗ | ✓ |
Infinity / NaN |
✗ | ✗ | ✓ |
استمرار \ في نهاية السطر |
✗ | ✗ | ✓ |
| نصوص متعددة الأسطر | ✗ | ✗ | ✓ (تُنفذ عبر الاستمرار) |
| قيمة اعتباطية في المستوى الأعلى | ✓ | ✓ | ✓ |
مواصفة JSON5 الكاملة تكاد لا تتجاوز صفحة؛ وحزمة json5 ظل المجتمع يصونها طويلًا، وتتصرف المتصفحات وNode بالطريقة نفسها.
3. التحليل: واجهتا API وقدرتان مختلفتان على التحديد
3.1 JSON5: تحليل بسيط للمستند كله
import JSON5 from 'json5';
const config = JSON5.parse(`
{
// service port
port: 8080,
host: '0.0.0.0',
features: ['json5', 'jsonc',],
retries: 0x10,
}
`);
console.log(config.port, config.retries); // 8080 16
يعيد JSON5.parse كائنًا عاديًا دون معلومات إزاحة — لأنه لا يستهدف سياقات التحرير، بل "قراءة الإعداد" فقط.
3.2 JSONC: شجرة صياغة بإزاحات
قيمة jsonc-parser لا تكمن في التحليل بل في التحرير. إنه يقدم ثلاث طبقات من API:
| API | الغرض | الاستخدام النموذجي |
|---|---|---|
parse |
التحليل إلى شجرة تحمل offset/length |
تلوين الصياغة والطي والتنقل |
visit |
مرور بعمق أولًا؛ يتلقى رد النداء مسار العقدة | فحص شبيه بـ JSONPath |
modify + applyEdits |
إنتاج تعديل نصي أدنى ثم تطبيقه على النص الأصلي | تغيير قيمة مع الحفاظ على التعليقات |
import { parseTree, findNodeAtLocation, modify, applyEdits } from 'jsonc-parser';
const text = `{
// port configuration
"server": {
"port": 8080 // 80 in production
}
}`;
const tree = parseTree(text);
const portNode = findNodeAtLocation(tree!, ['server', 'port']);
console.log(portNode?.offset, portNode?.length);
const edits = modify(text, ['server', 'port'], 3000, {
formattingOptions: { insertSpaces: true, tabSize: 2 },
});
console.log(applyEdits(text, edits));
يحفظ الخرج التعليقات الأصلية ويستبدل ثلاثة محارف فقط من 8080:
{
// port configuration
"server": {
"port": 3000 // 80 in production
}
}
هذا بالضبط ما لا يستطيعه "تحرير الإعداد بتعبيرات نمطية": فالتعبير النمطي لا يعرف أي 8080 يجب تغييره، ولا يعرف أن 8080 داخل نص يجب تركه وشأنه.
3.3 رسائل الخطأ يجب أن تحمل إزاحات
رموز خطأ jsonc-parser قيم enum؛ والشائعة منها:
| رمز الخطأ | المعنى |
|---|---|
InvalidSymbol |
ظهر محرف لا ينبغي أن يكون هنا |
PropertyNameExpected |
كان متوقعًا اسم مفتاح |
ColonExpected |
كان متوقعًا نقطتان |
CommaExpected |
كانت متوقعة فاصلة |
CloseBraceExpected |
لم يُغلق الكائن |
UnexpectedEndOfComment |
لم يُغلق التعليق |
InvalidEscapeCharacter |
هروب غير صالح |
بمجرد تحويل offset إلى سطر وعمود، يستطيع المحرر رسم الخط المتموج على الموضع المخالف بالضبط.
4. متى تستخدم أيًا منهما
| السياق | التوصية | السبب |
|---|---|---|
| أجسام طلبات واستجابات API العامة | JSON القياسي | اللهجات غير مدعومة في العملاء العامة |
package.json، composer.json |
JSON القياسي | مديرو الحزم يفهمون JSON القياسي فقط |
tsconfig.json، .vscode/settings.json |
JSONC | الافتراضي في المنظومة، ومدعوم أصليًا في سلسلة الأدوات |
| إعداد التطبيق (يحتاج تعليقات) | JSONC أو YAML | يجب الحفاظ على التعليقات مع بقاء الأدوات قادرة على القراءة والكتابة |
الحاجة إلى Infinity والست عشري والحرفيات المعقدة |
JSON5 | أقوى قدرة تعبيرية |
| تبادل البيانات والسجلات والذاكرات | JSON محدد الأسطر أو القياسي | مطلوب البث والعمومية |
قاعدة عملية: إن كان الملف سيقرأه "أداة غير بشرية"، ففضّل JSON القياسي؛ ولا تنظر في لهجة إلا عندما تكون واثقًا أنه لا يقرأه سوى شيفرتك.
5. ثلاثة أنواع من المخاطر تجلبها اللهجات
5.1 امتداد الحاجة إلى غيرها
عندما تقرأ أداة أخرى ملف إعداد JSON5 وتستدعي JSON.parse، فإنها تنهار تمامًا. العَرَض النموذجي هو "يعمل محليًا ويفشل في Docker" — لأن الحاوية تسلك مسار قراءة مختلفًا.
المواجهة: أضف فحصًا في CI إلى المستودع يدرج صراحة الملفات المسموح لها باستخدام لهجة؛ ويُتحقق من كل ما عداها كـ JSON قياسي.
5.2 مفاجآت حول استنتاج الأنواع
يسمح JSON5 بالمفاتيح غير المقتبسة وعلامات الاقتباس المفردة، لكنه لا يتيح إدخال إنشاءات حرفية مثل التواريخ أو التعبيرات النمطية — فهو ما زال صيغة بيانات، لا تقييم JavaScript.
افترض بعضهم أن JSON5 يستطيع التعبير عن new Date() ليروا التحليل يفشل. JSON5 ليس eval.
5.3 تعليقات تُسقط بصمت
لا يحفظ JSON5.stringify ولا JSON.stringify التعليقات. لذا فإن "اقرأه، وعدّل قيمة، واكتبه مرة أخرى" يفقد كل تعليق.
عندما يجب أن تبقى التعليقات، فالخيارات الوحيدة هي API التحرير في jsonc-parser، أو مكتبة YAML تدعم الحفاظ على التعليقات.
6. توصيات عملية
- [ ] أبقِ جدول "نوع الملف → الصيغة" في توثيق الفريق حتى لا يرتجل أحد؛
- [ ] غلّف قراءة الإعداد في
loadConfig()واحدة تقرر داخليًا أي محلل تستخدم، فلا تستدعي شيفرة العملJSON.parseمباشرة أبدًا؛ - [ ] عند تعديل إعداد بتعليقات برمجيًا، استخدم
modify+applyEdits؛ ولا تعد كتابة الملف كله أبدًا؛ - [ ] تحقق من ملفات اللهجات في CI أيضًا (
jsonc-parserلـ JSONC، وJSON5.parseلـ JSON5)، وإلا فلا تظهر أخطاء الصياغة إلا وقت التشغيل؛ - [ ] استخدم JSON القياسي للواجهات العامة ومفاتيح الذاكرة وحمولات طوابير الرسائل.
الأسئلة الشائعة
كيف أختار بين JSON5 وJSONC؟
اختر بحسب وقت التشغيل لا بحسب عدد الميزات الصياغية: يقرأ VS Code وTypeScript وESLint ملفات JSONC افتراضيًا، فـ JSONC يمنح أفضل توافق؛ ولا تلجأ إلى JSON5 إلا عندما تحتاج إلى صياغة حرفيات ES5.1 الأوسع، مثل علامات الاقتباس المفردة أو المفاتيح غير المقتبسة أو Infinity. ولا لهجة من الاثنتين مناسبة لاستجابات API العامة.
لماذا لا يستطيع محلل JSON قياسي قراءة JSONC مباشرة؟
يسمح JSONC بالتعليقات والفواصل الزائدة، بينما يقبل JSON.parse JSON وفق RFC 8259 فقط ويرمي عند أي منهما. تتطلب قراءة JSONC محللًا مثل jsonc-parser، أو إزالة التعليقات قبل تسليم النص إلى محلل قياسي.
هل يجب أن تبقى تعليقات JSONC بعد تعديلات الملف؟
نعم، لكن عبر API تحريرية فقط. تغيّر modify وapplyEdits في jsonc-parser العقدة المستهدفة مع الحفاظ على التعليقات الباقية؛ أما التحليل إلى كائن ثم تسلسله فيسقط كل تعليق.