حدود التحويل بين JSON ↔ YAML وXML
الخلاصة: الزعم بأن JSON مجموعة فرعية من YAML يضمن اتجاهًا واحدًا فقط، ولا يوجد لـ XML أي تحويل JSON رسمي على الإطلاق — كل شيء يقوم على الاصطلاح.

الخلاصة: استخدم JSON صيغةً وسيطة: حلّل المصدر إلى نموذج بيانات، ثم سلسل إلى الهدف؛ وقبل التحويل قرر هل يجب أن تصمد التعليقات وسمات XML ومساحاته الاسمية وأنواع الأعداد أو القيم المنطقية.
إن كان مسار تحويل صالحًا للاستخدام لا يتوقف على جمال الناتج، بل على وعيك بمقدار ما رمى به.
1. JSON وYAML: احتواء في اتجاه واحد
تنص مواصفة YAML 1.2 صراحةً: JSON مجموعة فرعية من YAML. أي أن أي JSON صالح هو YAML صالح:
{"name": "json", "tags": ["a", "b"], "count": 3}
هذا المقطع JSON وYAML بأسلوب التدفق في الوقت نفسه. والعكس غير صحيح.
| الميزة | JSON | YAML 1.2 |
|---|---|---|
| التعليقات | ✗ | ✓ |
| المراسي والأسماء المستعارة | ✗ | ✓ |
مستندات متعددة (---) |
✗ | ✓ |
| مفاتيح غير نصية | ✗ | ✓ (1: a، true: b) |
| مفاتيح معقدة | ✗ | ✓ (? [a, b]) |
| حساس للمسافات البادئة | ✗ | ✓ (ترفض المسافة البادئة بـ tab) |
| استنتاج الأنواع | صريح | ضمني (0o17، 0x1F، .inf) |
| قابلية القراءة في سطر واحد | جيدة | ضعيفة (المحتوى الطويل يعتمد على صياغة الطي) |
1.1 استنتاج الأنواع أكبر مزلق
يحلل YAML بعض القيم التي تشبه النصوص إلى أنواع أخرى:
zip: 01234 # string or octal integer? the YAML 1.2 core schema treats it as a string
version: 1.10 # 1.1 (a float); the semantics are lost
country: NO # a string in YAML 1.2; boolean false in YAML 1.1
date: 2026-10-30 # some implementations parse it as a date object
مثال NO أحرق كثيرين: مع محلل YAML 1.1 (بعض المكتبات الأقدم) يصبح country: NO هو false.
الاحتراز: ضع بين علامتي اقتباس كل معرّف "يشبه عددًا أو قيمة منطقية"، كما في version: "1.10".
1.2 المراسي والأسماء المستعارة والمستندات المتعددة
defaults: &defaults
retries: 3
timeout: 30
serviceA:
<<: *defaults
host: a.example.com
---
serviceB:
<<: *defaults
host: b.example.com
بعد توسيع المراسي والتحويل إلى JSON، تنتهي عادةً بكائني إعدادات مكررين، أو بمصفوفة واحدة (فالمستندات المتعددة يجب أن تصبح [{...}, {...}]).
وفي الحالتين، لا تُستعاد المراسي في التحويل العكسي — وهذه الخسارة لا مفر منها.
1.3 تحويل يحافظ على التعليقات
تُسقط معظم المكتبات التعليقات ببساطة أثناء التحويل. وعندما تحتاج إلى حفظها، استخدم Document API في حزمة yaml:
import { parseDocument } from 'yaml';
const document = parseDocument(`
# Service configuration
port: 8080
`);
document.set('port', 3000);
console.log(document.toString());
// # Service configuration
// port: 3000
الاختبار بسيط: إن كان هذا الملف سيُحرَّر من البشر مرارًا، فلا تسلك مسار "تحليل → كائن → تسلسل".
1.4 كيف تتحقق من اتساق الرحلة ذهابًا وعودة
/**
* Verifies round-trip consistency for JSON → YAML → JSON.
* @param sourceValue Original JSON value
* @param stringify YAML serialization function
* @param parse YAML parsing function
* @returns Whether the two results are exactly equal
*/
function isRoundTripStable(
sourceValue: unknown,
stringify: (input: unknown) => string,
parse: (input: string) => unknown,
): boolean {
const yamlText = stringify(sourceValue);
const restored = parse(yamlText);
return JSON.stringify(restored) === JSON.stringify(sourceValue);
}
تشغيله على بيانات حقيقية أنفع من قراءة التوثيق عشر مرات. وتأكد أن مجموعة عيناتك تغطي: null، والنص الفارغ، والمصفوفة الفارغة، و"00"، و"1e5"، والصفر السالب، وأعدادًا صحيحة أكبر من 2^53، ونصوصًا طويلة تحتوي أسطرًا جديدة.
2. JSON وXML: لا يوجد تحويل قياسي
يسبق XML الـ JSON بمسافة طويلة ويمتلك قدرات بنيوية أقوى بكثير (السمات، والمساحات الاسمية، والمحتوى المختلط، وDTD). وبين الاثنين لا توجد مواصفة تحويل رسمية؛ فكل أداة تفعل الشيء نفسه: تختار اصطلاحًا، ثم تحاول ألا تفقد كثيرًا.
2.1 الاصطلاحات الشائعة
| الاصطلاح | التمثيل | مثال |
|---|---|---|
| Element → مفتاح كائن | عنصر بالاسم نفسه يصبح حقلًا | <a>1</a> → {"a": "1"} |
Attribute → بادئة @ |
@name |
<a id="1"/> → {"a": {"@id": "1"}} |
Text → #text |
عندما توجد سمة ونص معًا | <a id="1">hi</a> → {"a": {"@id": "1", "#text": "hi"}} |
| عنصر مكرر → مصفوفة | حدوثان أو أكثر يصبحان مصفوفة | <i>1</i><i>2</i> → {"i": ["1","2"]} |
| Namespace → بادئة | ns:tag |
<ns:tag/> → {"ns:tag": ...} |
<order id="A1001">
<item sku="X1" qty="2">笔记本</item>
<item sku="X2" qty="1">钢笔</item>
</order>
{
"order": {
"@id": "A1001",
"item": [
{ "@sku": "X1", "@qty": "2", "#text": "笔记本" },
{ "@sku": "X2", "@qty": "1", "#text": "钢笔" }
]
}
}
2.2 خمس خسائر لا مفر منها
- ترتيب العناصر: كائنات JSON غير مرتبة، بينما XML تسلسل مرتب من العناصر؛
- السمات مقابل العناصر الابنة: تحمل دلالات مختلفة في XML، لكنها تصبح حقولًا عادية في JSON؛
- المحتوى المختلط: بنى مثل
<p>你好 <b>世界</b> 再见</p>لا يمكن التعبير عنها بكائن عادي؛ - إعلانات المساحات الاسمية:
xmlnsمفهوم نطاق؛ وبمجرد تسطيحه في حقول يذهب النطاق؛ - CDATA والكيانات:
<![CDATA[...]]>و&ليسا في JSON إلا نصًا.
2.3 معضلة "عنصر واحد أم مصفوفة"
<list><item>1</item></list>
مع item واحد يُرجع xml2js كائنًا افتراضيًا، ولا يُرجع مصفوفة إلا عند وجود اثنين.
وكنتيجة، تضطر شيفرة الواجهة الأمامية إلى كتابة Array.isArray(x) ? x : [x].
الاحتراز: فعّل خيار "دائمًا مصفوفة" عند التحليل، أو أعلن الحقل نوع مصفوفة استنادًا إلى مخطط.
import { parseStringPromise } from 'xml2js';
const parsed = await parseStringPromise(xmlText, {
explicitArray: true, // return an array even for a single element
trim: true,
explicitRoot: false,
});
2.4 تنبيه أمني
قبل تحليل XML غير الموثوق يجب تعطيل الكيانات الخارجية، وإلا فقد تُشغّل XXE (قراءة ملفات محلية، ومسح شبكات داخلية):
// In the Node ecosystem, DTDs and external entities are usually disabled through parser options
const parserOptions = { doctype: false, externalEntities: false };
التحويل من XML إلى JSON مجرد الطبقة الأولى؛ والتحويل لا يزيل سطح الهجوم الذي يجلبه XML.
3. أين يناسب كل من الصيغ الثلاث
| السيناريو | الصيغة الموصى بها | السبب |
|---|---|---|
| API HTTP العامة | JSON | مدعوم أصلًا في المتصفحات وكل اللغات |
| إعدادات تُصان يدويًا | YAML | التعليقات، والمسافات البادئة، والقراءة |
| مستندات تحتاج مخططًا قويًا ومساحات اسمية (SOAP، Office Open XML، SVG) | XML | القدرات البنيوية والنظام البيئي القائم |
| التفاعل مع أدوات التشغيل (K8s، CI) | YAML | الافتراضي في النظام البيئي |
| التخزين المؤقت والذاكرات والسجلات | JSON / JSONL | عالمي وبلا تبعيات |
4. ملاحظات التنفيذ
import { stringify as toYaml } from 'yaml';
/**
* Converts a JSON value to YAML text while keeping Chinese characters unescaped.
* @param value The JSON value to convert
* @returns The YAML text
*/
function jsonToYaml(value: unknown): string {
return toYaml(value, {
indent: 2,
lineWidth: 0, // disable automatic wrapping so long strings are not split
defaultStringType: 'QUOTE_DOUBLE',
});
}
بعض الخيارات العملية:
lineWidth: 0يعطّل اللف التلقائي؛ وإلا انقسمت الروابط الطويلة عبر الأسطر وصعب قراءتها؛- اقتباس كل شيء بعلامات مزدوجة يمنع قراءة قيم مثل
NOو1.10خطأً؛ - للبيانات ذات الدقة الرقمية الحساسة، حوّلها إلى نصوص قبل التسلسل.
وفي اتجاه XML، ثبّت دالة تحويل واحدة وضع "بادئة السمة" و"اسم مفتاح النص" و"استراتيجية المصفوفة" في توقيعها بدلًا من نثرها هنا وهناك:
type XmlMappingOptions = {
attributePrefix: string;
textKey: string;
alwaysArray: boolean;
};
const defaultMapping: XmlMappingOptions = {
attributePrefix: '@',
textKey: '#text',
alwaysArray: false,
};
5. قائمة تحقق
- [ ] كل معرّف "يشبه عددًا/قيمة منطقية" في YAML مقتبس؛
- [ ] مجموعة عينات تحقق لـ JSON → YAML → JSON محددة ومغطاة بالاختبارات؛
- [ ] عندما يجب حفظ التعليقات يُستخدم Document API بدلًا من رحلة كائن؛
- [ ] اصطلاح تحويل XML مدوَّن ويشارك الفريق مجموعة معاملات واحدة؛
- [ ] DTDs والكيانات الخارجية معطلة عند تحليل XML غير الموثوق؛
- [ ] تُقارن أعداد البايتات والحقول قبل التحويل وبعده، وأي شذوذ يثير تنبيهًا.
FAQ
كيف أختار اتجاه التحويل بين JSON وYAML وXML؟
فضّل JSON صيغةً وسيطة: حلّل YAML وXML إلى نموذج بيانات أولًا، ثم سلسل من ذلك النموذج بدلًا من إعادة كتابة النص. واستخدم Document API في YAML عندما يجب أن تصمد التعليقات، وثبّت اصطلاح تحويل XML واحدًا قبل أن تحتاج إلى السمات أو المساحات الاسمية.
لماذا يفقد التحويل من XML إلى JSON السمات والمساحات الاسمية؟
لا يمتلك JSON مفهوم السمات أو المساحات الاسمية، فيجب طي سمات XML وبادئاتها ومعرّفات URI للمساحات الاسمية في مفاتيح عادية، عادةً ببادئة @ أو عقدة #text. وبمجرد أن تتباين قواعد التحويل ينتج XML نفسه JSON مختلفًا ولا يمكن أن يعود كما كان.
هل أُبقي JSON وYAML وXML في مزامنة ثنائية الاتجاه طويلة الأمد؟
لا. التعليقات والمراسي وسمات XML ومساحاته الاسمية خسارة عبر الصيغ، والمزامنة ثنائية الاتجاه تستمر في تراكم الفروق. اختر صيغة مصدر واحدة وعامل الأخرى كتصديرات لمرة واحدة أو عروض مؤقتة.