مشكلة Norway في YAML والفروقات بين JSON وYAML التي يجب أن يعرفها المهندسون
كان نشراً اعتيادياً باستخدام Helm. أمضى الفريق يومين في ضبط ملف values.yaml لنشر متعدد المناطق. قام القالب بإنشاء ConfigMap في Kubernetes يحتوي على بيانات المنطقة المحلية — بما فيها رمز الدولة لمركز البيانات النرويجي. كتب أحدهم country: NO وأودعه. اجتاز خط أنابيب CI. خرج النشر.
ثم جاءت التنبيهات.
احتوى ConfigMap على country: false بدلاً من country: "NO". حصلت كل خدمة منبع قرأت حقل الدولة على قيمة منطقية بدلاً من سلسلة نصية. فشلت مقارنة السلاسل النصية. سقط منطق التوجيه إلى حالة افتراضية. انتهى بالحركة المرورية التي كان يجب أن تبقى في النرويج إلى أن تتم معالجتها بواسطة نقطة نهاية إقليمية خاطئة.
كان السبب الجذري سلسلة نصية واحدة غير مقتبسة في ملف YAML. تتعامل YAML 1.1 — الإصدار الذي تستخدمه جميع أدوات Kubernetes تقريباً — مع NO كقيمة منطقية false. وتُعامَل YES وON وOFF وY وN وno وyes وon وoff وy وn وعشرات الأشكال الأخرى بالطريقة ذاتها. بدون تحذير. بدون خطأ. خطأ صامت.
لا تعاني JSON من هذه المشكلة. {"country": "NO"} هي دائماً سلسلة نصية. تحويل النوع الضمني في YAML هو في آنٍ واحد أعظم ما تتميز به وأخطر فخاخها.
يتناول هذا الدليل الصورة الكاملة: لماذا توجد مشكلة Norway، وما الذي تغيّر في YAML 1.2 (ولماذا تتجاهله معظم الأدوات)، وكيفية كتابة استراتيجيات اقتباس صحيحة، وقواعد المسافة البادئة التي تُربك المبتدئين، وفخاخ دقة الأرقام، وأربعة سيناريوهات تحويل من العالم الحقيقي بدءاً من بيانات Kubernetes حتى خطط Terraform. عندما تحتاج إلى تسطيح قيمة JSON بأمان إلى YAML بدون هذا الفخ، يُقتبَس تلقائياً في محوّل JSON إلى YAML أي سلاسل نصية معرّضة لمشكلة Norway.
JSON مقابل YAML — متى تستخدم أيهما
قبل الغوص في مشكلة Norway، يُفيد فهم ما هو مُحسَّن حقاً لكل تنسيق. هما ليسا قابلَين للتبادل — لكل منهما مركز تصميم يجعله الخيار الأفضل في سياقات محددة.
| البُعد | JSON | YAML |
|---|---|---|
| الصياغة | صارمة — الأقواس وعلامات الاقتباس والفواصل مطلوبة | مرنة — مدفوعة بالمسافة البادئة، علامات ترقيم بسيطة |
| نظام الأنواع | صريح: سلسلة نصية، رقم، قيمة منطقية، null، مصفوفة، كائن | ضمني — يستنتج YAML 1.1 الأنواع من شكل القيمة |
| قابلية القراءة البشرية | صديق للمطورين، قابل للتحقق بالآلة | صديق للبشر، سهل التحرير اليدوي |
| متطلبات الاقتباس | السلاسل النصية مقتبسة دائماً | معظم القيم المقياسية يمكن أن تكون غير مقتبسة (مصدر مشكلة Norway) |
| التعليقات | غير مدعومة | مدعومة بـ # |
| الاستخدام الأساسي | واجهات برمجية، تبادل بيانات، أنظمة إعداد حديثة | Kubernetes، Docker Compose، Ansible، خطوط CI |
| التحليلات المفاجئة | لا شيء — تحليل صارم | نعم — Norway، أرقام ثمانية، طوابع زمنية |
| إنفاذ المخطط | نظام JSON Schema | مخطط YAML (أدوات أقل) |
تفوز JSON عندما تعبر بياناتك حدود الأنظمة — واجهات REST API وقوائم انتظار الرسائل وتسلسل قواعد البيانات. تُحلِّلها الآلات وتُولِّدها الآلات، والصياغة الصارمة تجعل التحقق منها مباشراً. استخدم منسّق JSON للتحقق من البنية قبل الإرسال.
يفوز YAML عندما يكون البشر المؤلفين الأساسيين. بيانات Kubernetes وسير عمل GitHub Actions وقوالب Helm وكتب تشغيل Ansible — هذه ملفات يقرأها المطورون ويحررونها عشرات المرات. علامات الترقيم المختصرة ودعم التعليقات تجعلها أكثر قابلية للصيانة من نظيراتها بصيغة JSON.
تنشأ المشكلة عند الحدود: عندما تُولِّد أداة ما JSON (مثل kubectl get deploy -o json أو terraform show -json) ويحتاج إنسان إلى إخضاع النتيجة للتحكم بالإصدارات أو تحريرها كـ YAML. هذا التحويل هو مكان إقامة مشكلة Norway. يتعامل محوّل YAML إلى JSON مع الاتجاه المعاكس عندما تحتاج إلى الرجوع.
مشكلة Norway — تعمق في التفاصيل
مشكلة Norway ليست خطأً. إنها ميزة في مواصفة YAML 1.1 تتصرف بالضبط كما صُمِّمت. فهم سبب تصميمها هكذا — ولماذا لا تزال العديد من الأنظمة تُطبِّق الإصدار 1.1 — هو المفتاح لتجنبها.
لماذا تُحلَّل “no” و”yes” و”on” و”off” و”y” و”n” بشكل خاطئ
عرَّفت مواصفة YAML 1.1 نوعاً منطقياً واسعاً كان يهدف إلى أن يكون صديقاً للبشر. اعترفت بجميع ما يلي كـ true أو false:
صحيح: y، Y، yes، Yes، YES، true، True، TRUE، on، On، ON
خطأ: n، N، no، No، NO، false، False، FALSE، off، Off، OFF
كانت النية حسنة: غالباً ما تستخدم ملفات الإعداد yes/no بدلاً من true/false في اللغة الإنجليزية، وأرادت YAML دعم الطريقة الطبيعية التي يكتب بها الناس الإعدادات. المشكلة هي أن yes وno وon وoff وy وn هي أيضاً قيم سلسلة نصية مشروعة تماماً تعني شيئاً مختلفاً تماماً في معظم التطبيقات.
إليك عدم التطابق في YAML بشكل ملموس:
# YAML 1.1 (what most parsers implement)
country: NO # parses as: country: false ← DANGER
enabled: yes # parses as: enabled: true
restart: off # parses as: restart: false
language: y # parses as: language: true
shell: n # parses as: shell: false
# Correct — explicit string quotes override type inference
country: "NO" # parses as: country: "NO" ← safe
enabled: "yes" # parses as: enabled: "yes"
restart: "off" # parses as: restart: "off"
language: "y" # parses as: language: "y"
shell: "n" # parses as: shell: "n"
ومقارنة JSON:
{"country": "NO"}
في JSON، NO داخل علامات اقتباس هي دائماً وبدون شروط سلسلة نصية. لا يوجد استنتاج ضمني للنوع. الصرامة التي تجعل JSON تبدو مطوّلة هي أيضاً ما يجعلها آمنة.
بالإضافة إلى تحويل القيم المنطقية، تُحوِّل YAML 1.1 أيضاً ضمنياً:
123e4→ الرقم1230000(تدوين علمي)0x1A→ الرقم26(ست عشري)0755→ الرقم493(ثماني — هذا يكسر سلاسل صلاحيات ملفات Unix)2024-05-04→ كائن تاريخ في كثير من المحللات (ليس مجرد سلسلة نصية)1_000_000→ الرقم1000000(فاصل شرطة سفلية)
مشكلة Norway هي في الواقع مجرد العضو الأكثر شهرة في عائلة كاملة من تحويلات النوع الضمني في YAML.
YAML 1.1 مقابل YAML 1.2 — ما الذي تغيّر
نُشرت YAML 1.2 عام 2009 — بعد أربع سنوات من YAML 1.1. هدفها الأساسي كان مواءمة YAML صراحةً مع JSON (إذ تُعدّ JSON في الواقع مجموعة فرعية صالحة من YAML 1.2) وتقليل تحويلات النوع الضمنية المفاجئة.
في YAML 1.2:
- يُضيَّق المنطقي إلى
trueوfalseفقط (حساس لحالة الأحرف). هذا كل شيء.yesوnoوonوoffهي سلاسل نصية عادية. - تتطلب الأحرف الثمانية البادئة
0o(0o755) — الشكل القديم0755هو سلسلة نصية. - لا تُحلَّل الطوابع الزمنية ضمنياً — تبقى
2024-05-04سلسلة نصية ما لم تُوسَم صراحةً. - المواصفة نفسها هي مجموعة شاملة لـ JSON، مما يعني أن كل وثيقة JSON صالحة هي أيضاً YAML 1.2 صالحة.
على الورق، تُحلّ YAML 1.2 مشكلة Norway كلياً. عملياً، بالكاد تحرّك النظام البيئي.
| المكتبة | المواصفة الافتراضية | خطر Norway |
|---|---|---|
| PyYAML (Python) | YAML 1.1 | نعم — لا يزال yaml.safe_load يُحلِّل NO كـ False |
| ruamel.yaml (Python) | YAML 1.2 (اختياري) | قابل للتهيئة — أأمن افتراضياً |
| js-yaml (Node.js) | YAML 1.1 | نعم في الإصدارات الأقدم؛ الإصدارات الأحدث لديها خيار FAILSAFE_SCHEMA |
| eemeli/yaml (Node.js) | YAML 1.2 | لا — 1.2 افتراضياً، أو قابل للتحديد صراحةً |
| gopkg.in/yaml.v2 (Go) | YAML 1.1 | نعم |
| gopkg.in/yaml.v3 (Go) | YAML 1.2 | أكثر أماناً بشكل ملحوظ |
| Kubernetes / Helm | YAML 1.1 (عبر Go yaml.v2) | نعم — تاريخي، يصعب جداً الترحيل منه |
| Ansible | YAML 1.1 (عبر PyYAML) | نعم |
سبب بطء الترحيل هو التوافق مع الإصدارات السابقة. الأنظمة التي اعتمدت على تحليل yes/no كقيم منطقية لعقد من الزمن لا تستطيع تغيير هذا السلوك بصمت دون كسر الإعدادات الموجودة. Kubernetes بشكل خاص قاعدة تثبيت ضخمة حيث يكون تغيير دلالات تحليل YAML تغييراً جذرياً على مستوى الكتلة.
الاستنتاج العملي: افترض دلالات YAML 1.1 في أي أداة لم تُهيِّئها صراحةً بشكل مختلف. اقتبس دائماً السلاسل النصية التي يمكن أن تُقرأ خطأً كقيم منطقية أو طوابع زمنية أو أرقام.
كيف تُصاب بها الأنظمة الإنتاجية
رمز الدولة النرويجي هو المثال الأكثر استشهاداً به لأنه غير بديهي — تبدو NO اختصاراً واضحاً، ليس قيمة منطقية. لكن النمط يتكرر عبر سيناريوهات العالم الحقيقي عديدة:
رموز المطارات IATA. المطار النرويجي Harstad/Narvik يحمل رمز EVE. آمن. مطار أوسلو Gardermoen هو OSL. آمن أيضاً. لكن أي تطبيق يستخدم YAML لتخزين رموز المطارات الإقليمية هو على بُعد رمز طريق no واحد من قيمة منطقية false في الإنتاج.
أسماء متغيرات البيئة. ON هي قيمة متغير بيئة مشروعة تماماً تعني “مُفعَّل” في بعض الأنظمة القديمة. OFF هي نظيرتها. ترحيل الإعدادات من سكريبتات الشل إلى YAML بدون اقتباس هذه القيم يُدخل تحويل نوع صامتاً.
حقول مستخدمي البريد الإلكتروني. مستخدم يكون اسمه الأول أو اسم المستخدم الخاص به حرفياً n أو y أو أياً من الكلمات المُشغِّلة سيُسلسَل بشكل خاطئ إذا كان التطبيق يُفرغ YAML بدون اقتباس صحيح. هذا خبيث بشكل خاص لأنه يفشل فقط لمجموعة فرعية من المستخدمين.
سياسات إعادة تشغيل Docker Compose. قيمة حقل restart_policy «"no"» تعني «لا تُعد تشغيل». إذا فقدت علامات الاقتباس في جولة YAML، تصبح القيمة false، وقد يُفسِّرها Docker Compose كـ «لا توجد سياسة إعادة تشغيل محددة» أو يطرح خطأ تحقق — في كلتا الحالتين، سلوك إعادة تشغيل الحاوية خاطئ.
حقل shell: في GitHub Actions. القيم الصالحة للشل هي bash وpwsh وpython وsh وcmd وpowershell. لا أيٌّ منها كلمة Norway. لكن من يكتب shell: yes أو shell: on كعنصر نائب أثناء التحرير قد يُفاجأ حين تُحوِّله YAML إلى قيمة منطقية قبل أن يراها المدقق حتى.
الحل في جميع الحالات واحد: اقتبس السلاسل النصية التي هي دلالياً سلاسل نصية، بصرف النظر عما إذا كان الإنسان يتعرف عليها ككلمات مفتاحية. يُطبِّق محوّل JSON إلى YAML هذا تلقائياً — تُقتبَس أي قيمة في قائمة كلمات Norway في الإخراج.
استراتيجية اقتباس السلاسل النصية
بمجرد فهم سبب عدم تطابق كلمات Norway، يكون الحل هو اختيار استراتيجية الاقتباس الصحيحة لحالتك. يدعم YAML ثلاثة أوضاع، لكل منها مفاضلات مختلفة.
التلقائي مقابل المزدوج مقابل المفرد
الاقتباس التلقائي (موصى به لمعظم التحويلات) يدع المكتبة تقرر متى تكون علامات الاقتباس ضرورية. القيم التي ستُقرأ خطأً بدون اقتباس — كلمات Norway والأرقام والطوابع الزمنية والسلاسل النصية التي تبدو كصياغة YAML — تُقتبَس تلقائياً. كل شيء آخر يبقى كقيمة مقياسية عادية. هذا يُنتج الإخراج الأكثر قابلية للقراءة مع البقاء آمناً.
# Auto mode output
name: Alice # plain — no ambiguity
country: "NO" # quoted — Norway word
age: 30 # plain — unambiguous number
created: "2024-05-04" # quoted — would otherwise parse as a date
port: "8080" # depends on library — some quote numeric-looking strings
السلاسل النصية بالاقتباس المزدوج تُحيط جميع قيم السلاسل النصية بعلامات اقتباس مزدوجة. هذا صريح وقابل للمراجعة — يرى أي قارئ أن جميع هذه القيم هي سلاسل نصية دون الاستدلال على المواصفة. المفاضلة هي الإسهاب وضعف قابلية القراءة البشرية، خاصةً للإعدادات المتداخلة بعمق.
# Double-quote mode
name: "Alice"
country: "NO"
replicas: "3" # even numbers become strings — may cause schema errors
كن حذراً: إذا توقّع مخططك المستهدف رقماً وقمت بتسلسله كسلسلة نصية مقتبسة، فإن محلل YAML سيُنوِّعه بشكل صحيح كسلسلة نصية، لكن قد يرفض Kubernetes أو مستهلك صارم آخر الحقل كنوع خاطئ.
السلاسل النصية بالاقتباس المفرد هي ميزة خاصة بـ YAML — لا توجد صياغة اقتباس مفرد في JSON. الاقتباس المفرد حرفي: لا تتابعات هروب بداخله. الحالة الخاصة الوحيدة هي أن علامة الاقتباس المفرد داخل سلسلة نصية مقتبسة بشكل مفرد يجب مضاعفتها (''). الاقتباس المفرد مثالي للسلاسل النصية التي تحتوي على شرطات مائلة عكسية أو أحرف خاصة تحتاج إلى هروب في الاقتباس المزدوج.
# Single-quote mode
pattern: 'C:\Users\alice\Documents' # no escape needed
regex: '\d+\.\d+' # backslashes literal
لتحويلات JSON إلى YAML المقصود منها الرجوع إلى JSON، فضّل الوضع التلقائي أو المزدوج. تُدخِل السلاسل النصية بالاقتباس المفرد صياغة خاصة بـ YAML تتطلب محللاً واعياً بـ YAML في الطريق المعاكس.
القيم المقياسية الكتلية (| و>)
صياغة القيمة المقياسية الكتلية في YAML مفيدة حقاً للسلاسل النصية متعددة الأسطر — شيء تتعامل معه JSON بشكل محرج بتتابعات الهروب \n.
القيمة المقياسية الكتلية الحرفية | تحفظ أسطر الجديد بالضبط:
# Literal block — newlines kept
script: |
#!/bin/bash
set -euo pipefail
echo "Starting deployment"
kubectl apply -f manifest.yaml
# Equivalent JSON representation (unreadable)
# {"script": "#!/bin/bash\nset -euo pipefail\necho \"Starting deployment\"\nkubectl apply -f manifest.yaml\n"}
القيمة المقياسية الكتلية المطوية > تُدمج الأسطر بمسافات، محوِّلةً كل سطر جديد إلى مسافة (باستثناء الأسطر الفارغة التي تصبح أسطراً جديدة):
# Folded block — newlines become spaces
description: >
This service handles authentication
for the entire platform. It supports
OAuth2, SAML, and API key authentication.
# Result: "This service handles authentication for the entire platform. It supports OAuth2, SAML, and API key authentication.\n"
تتألق القيم المقياسية الكتلية لتضمين شهادات TLS أو سكريبتات الشل متعددة الأسطر أو استعلامات SQL في إعدادات YAML — سيناريوهات يكون فيها النظير بصيغة JSON سطراً طويلاً مهروباً لا يمكن لأي إنسان قراءته.
عند التحويل من JSON إلى YAML، تستخدم معظم المحوّلات (بما في ذلك أداتنا) الوضع التلقائي وتُمثِّل السلاسل النصية متعددة الأسطر بقيم مقياسية كتلية فقط عند اكتشاف أسطر جديدة مضمّنة. تحصل السلاسل النصية أحادية السطر على قيم مقياسية تدفقية (مقتبسة أو عادية). استخدم محوّل JSON إلى YAML لرؤية الإخراج قبل إيداعه في بيان.
المسافة البادئة — مسافتان أو 4 مسافات، والجدولة محظورة
قواعد المسافة البادئة في YAML أصرم مما تبدو عليه. المواصفة لها قاعدة مطلقة واحدة واتفاقية واحدة تتفاوت حسب النظام البيئي.
القاعدة المطلقة: علامات الجدولة محظورة. يجب أن يستخدم كل مستوى مسافة بادئة مسافات. حرف الجدولة في ملف YAML هو خطأ في التحليل في معظم المحللات:
# WRONG — tabs cause parse errors
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app # ← tab character here → ParseError
# CORRECT — spaces only
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app # ← two spaces
رسالة الخطأ التي ستراها تتفاوت حسب المكتبة. في PyYAML الخاصة بـ Python:
yaml.scanner.ScannerError: while scanning for the next token
found character '\t' that cannot start any token
في yaml.v3 الخاص بـ Go:
yaml: line 4: found character that cannot start any token
هيِّئ محررك لتوسيع علامات الجدولة إلى مسافات لملفات YAML. في VS Code، أضف إلى إعدادات مساحة عملك: "[yaml]": { "editor.insertSpaces": true, "editor.tabSize": 2 }.
الاتفاقية: مسافتان مقابل 4 مسافات. كلاهما صالح. الاتفاقيات تختلف حسب النظام البيئي:
| النظام البيئي | الاتفاقية | السبب |
|---|---|---|
| بيانات Kubernetes | مسافتان | المستندات والأمثلة الرسمية تستخدم 2 |
| قوالب Helm | مسافتان | تتبع اتفاقية K8s |
| Docker Compose | مسافتان | أمثلة مواصفة compose الرسمية |
| GitHub Actions | مسافتان | أمثلة سير العمل الرسمية |
| كتب تشغيل Ansible | مسافتان | التوثيق الرسمي |
| الإعدادات التقليدية | 4 مسافات | يطابق افتراضي تجميل JSON |
لأي ملف سيستهلكه Kubernetes أو Docker Compose، استخدم مسافتين. لملفات الإعداد المستقلة التي سيقرأها البشر فقط والأدوات المخصصة، كلاهما يعمل — فقط كن متسقاً داخل الملف. يُعيِّن محوّل JSON إلى YAML مسافتين افتراضياً ويتيح لك التبديل إلى 4 للمشاريع التي تُفضِّلها.
قاعدة أخرى: يجب أن تكون العناصر الابنة بمسافة بادئة أكثر من آبائها، لكن يمكن أن يكون عدد المسافات الإضافية أي عدد صحيح موجب (1، 2، 3، 4…) — طالما كان متسقاً داخل كتلة. عملياً، استخدم دائماً 2 أو 4 لقابلية القراءة.
معالجة الأرقام عبر JSON ↔ YAML
يدعم كلا التنسيقين الأرقام، لكن الحالات الحافة تختلف بما يكفي لإحداث أعطال إنتاجية.
فقدان الدقة للأرقام الكبيرة
نوع Number في JavaScript هو عدد عشري وفق معيار IEEE 754 مزدوج الدقة. يمكنه تمثيل الأعداد الصحيحة بدقة حتى 2^53 − 1 = 9,007,199,254,740,991. وراء ذلك، تضيع دقة الأعداد الصحيحة:
// JavaScript precision loss — this is not a YAML problem, but it affects JSON parsing
JSON.parse('{"v": 9007199254740993}').v
// → 9007199254740992 (the 3 became 2 — one bit lost)
// Safe — within 2^53 range
JSON.parse('{"v": 9007199254740991}').v
// → 9007199254740991 (exact)
هذا مهم لتحويل JSON إلى YAML في بيئات JavaScript لأن الدقة تضيع قبل بدء تسلسل YAML. حقل metadata.resourceVersion في Kubernetes هو حقل سلسلة نصية تحديداً لأن إصدارات الموارد يمكن أن تتجاوز نطاق الأعداد الصحيحة الآمنة. حقول أخرى تبدو كأرقام صغيرة — observedGeneration، مكونات uid — أأمن، لكن أي حقل int64 في استجابة K8s يحتمل أن يكون متأثراً.
الحلول:
- استخدم Python أو Go لخطوط أنابيب التحويل التي تتضمن أرقاماً كبيرة — كلاهما يتعامل مع الأعداد الصحيحة الاعتباطية بشكل أصلي.
- في Node.js، استخدم محلل JSON يدعم BigInt:
JSON.parse(text, (_, v) => typeof v === 'number' && !Number.isSafeInteger(v) ? BigInt(v) : v). - للحقول التي يجب أن تتحول بدون فقدان، سلسِلها كسلاسل نصية عند المصدر.
- عند مراجعة YAML المُحوَّل، ابحث عن حقول مثل
resourceVersionوgenerationوالقيم المشتقة من الطوابع الزمنية.
غرائب الأرقام الثمانية والست عشرية
تتعامل YAML 1.1 مع بعض السلاسل الشبيهة بالأرقام كأعداد صحيحة غير عشرية:
# YAML 1.1 parsing surprises
permissions: 0755 # parses as octal 493, not decimal 755
value: 0x1A # parses as hex 26, not string "0x1A"
# YAML 1.2 behavior
permissions: 0755 # stays as integer 755 (decimal) — octal requires 0o prefix
permissions: 0o755 # parses as octal 493 in both 1.1 and 1.2
# Safe for both specs — quote any leading-zero value
permissions: "0755" # always the string "0755"
فخ الأرقام الثمانية خطير بشكل خاص لصلاحيات ملفات Unix، ومكونات عناوين IP ذات الأصفار البادئة (بعض أجهزة الشبكة)، وأي رمز رقمي يستخدم أصفاراً بادئة للتبطين (الرموز البريدية، رموز المنتجات). اقتبس دائماً هذه القيم عند كتابة YAML يدوياً، أو تأكد من أن محوّلك يقتبسها — يكتشف محوّل JSON إلى YAML السلاسل النصية الرقمية من JSON ويحفظ نوعها كسلسلة نصية.
التحويلات من العالم الحقيقي
تصبح مشكلة Norway واستراتيجيات الاقتباس ملموسة عند تطبيقها على سيناريوهات التحويل الحقيقية.
بيان Kubernetes من JSON
سير العمل القياسي: يُعطيك kubectl get deploy my-app -o json الكائن المباشر بصيغة JSON. تريد تنظيفه (إزالة status وcreationTimestamp والحقول المُدارة) وإيداعه في git كبيان YAML.
مصدر JSON (مختصر):
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"name": "my-app",
"namespace": "production",
"labels": {
"app": "my-app",
"region": "NO"
}
},
"spec": {
"replicas": 3,
"selector": {
"matchLabels": { "app": "my-app" }
},
"template": {
"spec": {
"containers": [{
"name": "app",
"image": "registry.example.com/my-app:v1.2.3",
"env": [
{ "name": "REGION", "value": "NO" },
{ "name": "ENABLE_FEATURE", "value": "yes" }
]
}]
}
}
}
}
إخراج YAML المتوقع (مع حماية Norway):
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
namespace: production
labels:
app: my-app
region: "NO" # quoted — Norway word
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
spec:
containers:
- name: app
image: registry.example.com/my-app:v1.2.3
env:
- name: REGION
value: "NO" # quoted — Norway word
- name: ENABLE_FEATURE
value: "yes" # quoted — Norway word
لاحظ أن replicas: 3 يُترك بدون اقتباس — فهو عدد صحيح مشروع تتوقعه Kubernetes كرقم. كلمات Norway في قيم labels وenv مقتبسة. سيُنتج محوّل ساذج لا يتعامل مع قيم YAML 1.1 المنطقية بصمت region: false وvalue: false.
بعد التحويل، تحقق بأمر: kubectl apply --dry-run=client -f manifest.yaml. هذا يُمسك الأخطاء الهيكلية دون لمس الكتلة.
جرّب التحويل في محوّل JSON إلى YAML — الصق JSON أعلاه وشاهد إخراجاً آمناً من مشكلة Norway فوراً. استخدم محوّل YAML إلى JSON للتحقق من الجولة الكاملة ذهاباً وإياباً.
Docker Compose من JSON
تُولِّد خطوط أنابيب CI/CD أحياناً إعدادات Docker Compose برمجياً من مخزن إعداد JSON، ثم تكتبها على القرص كـ YAML ليقرأها المطورون.
الفخ الحرج — سياسة إعادة التشغيل:
{"restart_policy": "no"}
في Compose، restart_policy: "no" قيمة صالحة تعني «لا تُعد تشغيل الحاوية أبداً». بدون علامات اقتباس في YAML، يصبح هذا restart_policy: false، الذي قد تتعامل معه Docker Compose إما كنفس الدلالة (falsى = لا إعادة تشغيل) أو ترفضه بخطأ تحقق من النوع — يتفاوت السلوك حسب إصدار Compose. الاقتباس إلزامي.
احذر أيضاً: قيمة on-failure لـ deploy.restart_policy.condition في Compose v3 — تحتوي على الكلمة on لكنها مواصِلة ولا تُطابق قائمة المُشغِّلات، لذا هي آمنة فعلاً. لكن condition: on (بدون -failure) ستُحدث عدم تطابق. اقتبس قيم متغيرات البيئة في كتلة environment: إذا كانت يمكن أن تكون كلمات Norway.
تحقق من ملفات Compose بعد التحويل: يُحلِّل docker-compose config ويُعيد إخراج الشكل القياسي، كاشفاً عن أخطاء النوع.
سير عمل GitHub Actions
ملفات سير عمل GitHub Actions هي ملفات YAML يحررها المطورون يدوياً. سيناريو التحويل الأكثر شيوعاً هو قراءة بيانات سير العمل من GitHub API (التي تُعيد JSON) وتحويلها إلى ملف YAML محلي للتحرير.
الحقول الرئيسية التي يجب مراقبتها:
# SAFE — no Norway words in standard GitHub Actions
on: # "on" is a YAML key here, not a value — handled differently
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run tests
run: |
npm install
npm test
env:
NODE_ENV: production # safe — not a Norway word
DEBUG: "off" # Norway word in value — needs quoting
ملاحظة: on: كمفتاح YAML خاص — تنطبق مشكلة Norway على القيم وليس المفاتيح. لكن on كقيمة (مثل DEBUG: on) ستُشغِّل التحويل. تستحق كتلة env: تدقيقاً خاصاً لأن قيم متغيرات البيئة هي سلاسل نصية، لكن كثيراً منها علامات قصيرة يمكن أن تتعارض مع كلمات Norway.
خطة Terraform JSON → YAML
يُخرِج terraform show -json tfplan تمثيلاً JSON تفصيلياً لما تخطط Terraform لإنشائه أو تعديله أو إتلافه. تحويل هذا إلى YAML يجعله أكثر قابلية للقراءة لمراجعات طلبات السحب وعمليات تدقيق الامتثال.
terraform plan -out=tfplan
terraform show -json tfplan > plan.json
# Then convert with our tool or a library
ملف JSON لخطة Terraform معقد وعميق. المخاوف الرئيسية عند التحويل:
-
معرّفات الأعداد الكبيرة. يمكن أن تكون معرّفات موارد السحابة (معرّفات حسابات AWS، أرقام مشاريع GCP) أرقاماً كبيرة. قم بالتحويل عبر Python أو Go لتفادي فقدان دقة
float64. -
سلاسل نصية لقيود الإصدار. تستخدم Terraform
~>و>=و<=في قيود إصدار المزوّد. هذه قيم سلسلة نصية تتعامل معها YAML بشكل صحيح طالما أنها ليست كلمات Norway. -
قيم إعداد المزوّد. يمكن أن تتضمن إخراجات خطة Terraform قيم إعداد للموارد. إذا كان حقل منطقي يُعيَّن افتراضياً كـ
falseويُمثَّل كـ"no"في بعض مخططات المزوّد، فهذا خطر Norway في الطريق إلى YAML. -
كتلة
.sensitive_values. القيم الحساسة مُنقَّحة كقيم منطقيةtrueفي ملف JSON للخطة. هذه تنجو من التحويل بنظافة إذtrueليست كلمة Norway في أي إصدار YAML.
تحويل Terraform إلى YAML هو أساساً للمراجعة البشرية، وليس لإعادة التغذية في Terraform. لا تستخدم بيانات YAML كمدخل لـ Terraform — التنسيق الأصلي لـ Terraform هو HCL، وتنسيق مدخل JSON خاص وموثَّق بشكل منفصل.
أمثلة كود — 4 لغات
Node.js (eemeli/yaml + js-yaml)
نظام Node.js البيئي لديه مكتبتان مهيمنتان لـ YAML مع معالجة مختلفة جوهرياً لمشكلة Norway:
// eemeli/yaml — recommended, YAML 1.2 by default, Norway-safe
import { stringify } from 'yaml';
import { readFileSync } from 'fs';
const jsonInput = readFileSync('input.json', 'utf8');
const data = JSON.parse(jsonInput);
// Default: YAML 1.2 — "NO" stays as "NO", no boolean coercion
const yamlOutput = stringify(data);
console.log(yamlOutput);
// region: NO ← safe in 1.2, but for maximum compatibility quote it explicitly
// Force YAML 1.1 behavior (for K8s/Helm environments that parse 1.1)
const yamlForK8s = stringify(data, { version: '1.1' });
// region: 'NO' ← auto-quoted because 1.1 would parse NO as false
console.log(yamlForK8s);
// js-yaml — widespread, but YAML 1.1 semantics, Norway-risky without care
import yaml from 'js-yaml';
import { readFileSync } from 'fs';
const data = JSON.parse(readFileSync('input.json', 'utf8'));
// Default dump — Norway words may not be quoted
const unsafe = yaml.dump(data);
// region: NO ← will parse as false if re-read by a 1.1 parser!
// Safer: use a custom schema or force quoting
const safer = yaml.dump(data, {
schema: yaml.JSON_SCHEMA, // restricts to JSON-compatible types
noCompatMode: false,
lineWidth: -1,
quotingType: '"',
forceQuotes: false, // only quotes when necessary per JSON schema
});
للمشاريع الجديدة، فضّل eemeli/yaml. إعداد YAML 1.2 الافتراضي أأمن، وواجهة Document API الخاصة به توفر تحكماً دقيقاً في الاقتباس، وتتعامل مع دقة الجولة الكاملة بشكل أفضل. للمشاريع التي تستخدم بالفعل js-yaml، استخدم خيار JSON_SCHEMA للتقييد بالأنواع الآمنة لـ JSON.
Python (PyYAML + ruamel.yaml)
Python هي اللغة المهيمنة لأدوات Kubernetes وAnsible وخطوط أنابيب هندسة البيانات — جميعها مستخدمون ثقيلون لـ YAML.
import json
import yaml
import sys
# PyYAML — simple, standard, but YAML 1.1 by default
with open('input.json') as f:
data = json.load(f)
output = yaml.dump(data, default_flow_style=False, allow_unicode=True)
# country: 'NO' ← PyYAML is actually smart enough to auto-quote Norway words
# But it does NOT quote "yes", "no" (lowercase) in all configurations:
# enabled: 'yes' ← quoted
# tag: y ← may or may not be quoted depending on version
print(output)
import json
import sys
from ruamel.yaml import YAML
# ruamel.yaml — round-trip fidelity, supports YAML 1.2, recommended for production
yaml_rt = YAML()
yaml_rt.default_flow_style = False
yaml_rt.width = 4096 # prevent unwanted line wrapping
yaml_rt.best_map_flow_style = False
with open('input.json') as f:
data = json.load(f)
yaml_rt.dump(data, sys.stdout)
# Preserves key order, handles Norway words correctly, supports anchors on round-trip
لسكريبتات أتمتة Ansible وKubernetes حيث تُحوِّل استجابات JSON API إلى بيانات YAML، ruamel.yaml هو الخيار الأأمن. PyYAML جيدة للسكريبتات البسيطة حيث تتحكم في بيانات الإدخال وتحققت من عدم وجود كلمات Norway.
Go (gopkg.in/yaml.v3)
Go هي لغة نظام Kubernetes البيئي نفسه — kubectl وHelm وArgo وFlux ومعظم مشغّلات K8s مكتوبة بـ Go.
package main
import (
"encoding/json"
"fmt"
"os"
"gopkg.in/yaml.v3"
)
func main() {
// Read JSON input
jsonBytes, err := os.ReadFile("input.json")
if err != nil {
panic(err)
}
// Unmarshal JSON into a generic map
var data map[string]interface{}
if err := json.Unmarshal(jsonBytes, &data); err != nil {
panic(err)
}
// Marshal to YAML — yaml.v3 uses YAML 1.2 semantics
yamlBytes, err := yaml.Marshal(data)
if err != nil {
panic(err)
}
fmt.Println(string(yamlBytes))
// country: "NO" ← yaml.v3 quotes Norway words correctly
// replicas: 3 ← integers stay integers
// enabled: true ← booleans stay booleans
}
yaml.v3 تحسّن كبير على yaml.v2 من حيث أمان مشكلة Norway. كانت مكتبة v2 تتبع YAML 1.1 وتكتب NO بدون اقتباس؛ تُقتبَس القيم الغامضة بشكل صحيح في v3. إذا كنت تصون مشروع Go قديماً يستخدم v2، قم بالترقية إلى v3 — واجهة البرمجة متوافقة إلى حد بعيد وتحسين الأمان يستحق الترحيل.
سطر أوامر Bash (yq + jq)
للسكريبتات الشلية والتحويلات السريعة لمرة واحدة، يُحوِّل yq (إصدار Mike Farah، mikefarah/yq) JSON إلى YAML في أمر واحد:
# Install yq
brew install yq # macOS
sudo wget -qO /usr/local/bin/yq \
https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64
chmod +x /usr/local/bin/yq # Linux
# Convert JSON file to YAML
yq -P < input.json > output.yaml
# Convert from kubectl JSON output
kubectl get deploy my-app -o json | yq -P > manifest.yaml
# Pipe through jq first to filter/transform, then convert to YAML
kubectl get deploy my-app -o json \
| jq 'del(.status, .metadata.creationTimestamp, .metadata.managedFields)' \
| yq -P > clean-manifest.yaml
نمط jq | yq هو نمط قوي: استخدم jq لمعالجة JSON (تصفية الحقول، إعادة تشكيل البنية، الاستعلام عن القيم) وyq -P كمُسلسِل YAML النهائي.
تحذير مشكلة Norway مع yq: يحترم yq (mikefarah) نوع الإدخال من JSON — السلسلة النصية JSON "NO" في الإدخال ستُسلسَل كسلسلة نصية YAML مع علامات اقتباس. لكن إذا ولّدت YAML مباشرة بـ yq (وليس من مدخل JSON)، يجب اقتباس قيم كلمات Norway صراحةً. استخدم محوّل YAML إلى JSON للتحقق من الجولة الكاملة بعد إخراج yq.
الحالات الحافة والتحذيرات
بالإضافة إلى مشكلة Norway، هناك عدة حالات حافة في تحويل JSON ↔ YAML تُربك المهندسين ذوي الخبرة:
-
YAML متعدد الوثائق (فاصل
---). يمكن أن يحتوي ملف YAML الواحد على وثائق متعددة مفصولة بـ---. لا يوجد مفهوم مكافئ في JSON. عند تحويل YAML متعدد الوثائق إلى JSON، تأخذ معظم الأدوات إما الوثيقة الأولى فقط أو تدمج جميع الوثائق في مصفوفة. حدد سلوكك ووثّقه صراحةً لخطوط الأنابيب التي قد تصطدم بملفات متعددة الوثائق. -
مراسي YAML ومراجعها. يدعم YAML تعريفات
&anchorومراجع*aliasلإعدادات لا تُكرّر نفسها. عند تحويل YAML إلى JSON، يجب توسيع المراسي — قد يكون الـ JSON الناتج أكبر بكثير من مصدر YAML. عند تحويل JSON إلى YAML، لا يستطيع المحوّل إعادة بناء المراسي التي لم تكن موجودة في الأصل. -
التحليل الضمني للطوابع الزمنية. تُحوِّل محللات YAML 1.1
2024-05-04و2024-05-04T12:00:00Zإلى كائنات تاريخ أصلية للغة، وليس إلى سلاسل نصية. عند تسلسل هذا الكائن بتاريخ مرة أخرى إلى JSON، يعتمد الإخراج على المكتبة. الدوران بالتواريخ عبر YAML بدون اقتباس صريح للسلاسل النصية ("2024-05-04") يمكن أن يُغيِّر التنسيق بصمت. -
وسم
!!binary. يمكن لـ YAML تضمين بيانات ثنائية مُرمَّزة بـ base64 بوسم!!binary. لا يوجد نوع ثنائي في JSON — يجب أن تكون الثنائيات سلسلة نصية بـ base64. تستخدم Kubernetes!!binaryلبعض قيم السر. -
تعارض أنواع المفاتيح. تتطلب JSON أن تكون مفاتيح الكائن سلاسل نصية. يسمح YAML بمفاتيح من أي نوع — مفاتيح صحيحة ومنطقية وحتى مفاتيح كائنات معقدة. لا يمكن تمثيل ملف YAML بمفاتيح
true: valueأو1: valueبإخلاص كـ JSON. تُحوِّل معظم المحوّلات المفاتيح إلى سلاسل نصية، لكن الدلالات تتغيّر. -
تباين تمثيل null. في YAML،
nullو~وNullوNULLوقيمة فارغة تعني جميعها null. في JSON،nullفقط هي null. عند تحويل YAML إلى JSON، تتطبَّع جميع هذه علىnull. لكن عند التحويل من JSON مرة أخرى إلى YAML، اختيار تمثيل null مهم. -
تغييرات ترتيب الفرز. كائنات JSON تقنياً ليس لها ترتيب مفاتيح محدد (رغم أن معظم المحللات تحفظ ترتيب الإدراج). وبالمثل تعيينات YAML. لكن بعض مكتبات YAML ترتب المفاتيح أبجدياً افتراضياً عند التسلسل. هيِّئ
sort_keys=Falseفي PyYAML والخيارات المكافئة في المكتبات الأخرى.
متى لا يجب التحويل
التحويل ليس دائماً الإجابة الصحيحة. إليك السيناريوهات التي يكون فيها البقاء في التنسيق الأصلي خياراً أفضل:
لا تُحوِّل YAML إلى JSON إذا احتوى YAML على تعليقات توثِّق منطق الأعمال. تعليقات YAML ليست جزءاً من نموذج البيانات — تختفي في أي تسلسل إلى JSON. إذا كان بيان Kubernetes يحتوي على تعليقات تشرح لماذا اختير حد موارد محدد أو لماذا مُنح استثناء سياسة أمان، فإن التحويل إلى JSON يُتلف هذا التوثيق. احتفظ بـ YAML.
لا تُحوِّل الإعدادات تلقائياً في خطوط أنابيب CI بدون اختبارات الجولة الكاملة. إذا كان خط أنابيبك يُحوِّل JSON إلى YAML ثم يُطبِّق YAML على كتلة، أضف خطوة تحقق من الجولة الكاملة: YAML مرة أخرى إلى JSON، ثم قارن مع الأصل. هذا يُمسك مفاجآت تحويل النوع قبل أن تصل إلى الإنتاج.
لا تُحوِّل فقط لأن أداة ما تُخرج JSON. تُخرِج kubectl وaws وterraform وdocker inspect كلها JSON، لكن معظم هذه الأدوات تقبل أيضاً YAML كمدخل. قبل بناء خطوة تحويل، تحقق مما إذا كانت الأداة المستهدفة يمكنها قبول مدخل YAML مباشرة — معظم أدوات DevOps الحديثة يمكنها ذلك.
لا تُحوِّل إذا كانت المخططات تختلف. إذا كان JSON الخاص بك يستخدم مفاتيح camelCase ومستهلك YAML يتوقع snake_case (أو العكس)، تحتاج إلى خطوة تحويل بالإضافة إلى تحويل التنسيق. تحويل التنسيق المجرد سيُنتج YAML صحيح صياغياً لكن خاطئ دلالياً.
لا تحتفظ بكلا التنسيقين في تزامن يدوي. إذا كنت تصون config.json وconfig.yaml يُفترض أنهما مكافئان، ستنجرف. اختر تنسيقاً قياسياً واحداً واستنتج الآخر تلقائياً.
الأسئلة الشائعة
هل لا تزال مشكلة Norway في YAML تؤثر على الأنظمة الحديثة؟
نعم — فهي منتشرة في النظام البيئي. تستخدم Kubernetes وHelm مكتبة yaml.v2 الخاصة بـ Go (دلالات YAML 1.1) في أجزاء مهمة من قواعد الكود الخاصة بهما. تستخدم Ansible PyYAML (YAML 1.1). تُحلَّل ملفات YAML لسير عمل GitHub Actions بواسطة محلل YAML الداخلي لـ GitHub ذو السلوك الخاص. افترض دلالات الإصدار 1.1 حتى تتحقق من خلاف ذلك.
لماذا أُحوِّل JSON إلى YAML إذا كانت YAML أصعب في التحليل؟
التحويل لا يتعلق بصعوبة المحلل — يتعلق بقابلية التحرير البشري. JSON مثالية للآلات؛ YAML مثالية للبشر الذين يحتاجون إلى قراءة الملفات وتحريرها ومراجعتها. يجب أن يكون بيان Kubernetes المودَع في git والمراجَع في طلبات السحب والمُضبَّط يدوياً بواسطة المهندسين بصيغة YAML. يجب أن يكون البيان ذاته المُسترجَع من واجهة البرمجة للمعالجة البرمجية بصيغة JSON. يجسر محوّل JSON إلى YAML العالمَين.
هل يمكنني تحويل JSON ↔ YAML ذهاباً وإياباً بدون فقدان؟
مع تحفظات، نعم — للبيانات المتوافقة مع JSON. JSON هي مجموعة فرعية من YAML 1.2، لذا أي وثيقة JSON صالحة هي YAML 1.2 صالحة. يجب أن تكون الجولة JSON → YAML → JSON بدون فقدان لأي بيانات بدون تحويل ضمني للنوع. استخدم مكتبة YAML 1.2 لكلا الاتجاهين لضمان جولات كاملة بدون فقدان.
ما أأمن مكتبة YAML للإنتاج؟
لـ Python: ruamel.yaml مُهيَّأة لـ YAML 1.2. لـ Node.js: eemeli/yaml (حزمة yaml على npm). لـ Go: gopkg.in/yaml.v3. جميعها تُطبِّق دلالات YAML 1.2 أو لها أوضاع YAML 1.2 صريحة وتتعامل مع كلمات Norway بشكل صحيح. تجنّب مكتبات YAML 1.1 في المشاريع الجديدة.
هل يدعم بيان Kubernetes YAML التعليقات بعد تحويل JSON؟
لا — لا يمكن استعادة التعليقات من JSON. لا توجد صياغة تعليق في JSON، لذا لا يوجد شيء للتحويل. عند تشغيل kubectl get deploy -o json وتحويل الإخراج إلى YAML للتخزين في git، لن يحتوي YAML الناتج على تعليقات. يجب كتابة التعليقات في بيان Kubernetes بواسطة إنسان بعد التحويل.
كيف أتعامل مع الأعداد الصحيحة الكبيرة مثل resourceVersion أو الطوابع الزمنية بالنانوثانية؟
حقل metadata.resourceVersion في Kubernetes هو حقل سلسلة نصية عمداً — عرف فريق Kubernetes أن محللات JSON في JavaScript والأوقات التشغيلية الأخرى المبنية على float64 ستفقد الدقة مع الأعداد الصحيحة الكبيرة. عامله دائماً كسلسلة نصية. للأعداد الصحيحة الكبيرة الرقمية حقيقياً (مثل الطوابع الزمنية بالنانوثانية في بعض أنظمة التتبع)، استخدم نوع int في Python أو int64 في Go أو BigInt في Node.js للتحليل. لا تمررها أبداً عبر JSON.parse() في JavaScript بدون دالة محوّل مخصصة.
هل تبنّي YAML 1.2 واسع النطاق بعد؟
بشكل غير منتظم. المكتبات الرئيسية للغات تُرحِّل: yaml.v3 لـ Go وruamel.yaml لـ Python وeemeli/yaml لـ Node.js جميعها تدعم أو تُعيِّن YAML 1.2 افتراضياً. لكن Kubernetes وAnsible وجزء كبير من نظام DevOps البيئي لا يزال يعمل على محللات YAML 1.1 بسبب التكلفة العالية للتوافق مع الإصدارات السابقة.
هل يجب على فريقنا توحيد قياسنا على JSON أو YAML للإعدادات؟
قِس على الغرض وليس على التنسيق. استخدم JSON للإعدادات التي تستهلكها الشفرة (أجسام طلبات API، ملفات إعداد SDK، أدوات برمجية). استخدم YAML للإعدادات التي يستهلكها البشر (بيانات Kubernetes، خطوط CI، إعدادات النشر، كتب تشغيل Ansible). تجنّب المزج بين التنسيقين للإعداد ذاته — اختر تمثيلاً واحداً لكل نوع إعداد وأتمِت التحويل إذا احتجت إلى كليهما. عندما تحتاج إلى التحويل، يعمل كل من محوّل JSON إلى YAML ومحوّل YAML إلى JSON كلياً في متصفحك — لا تغادر بياناتك جهازك.
جرّب الآن
هل أنت مستعد لتحويل ملف حقيقي؟ جرّب محوّل JSON إلى YAML لتحويل JSON إلى YAML آمن لـ Kubernetes — يُقتبَس تلقائياً كلمات Norway (NO وyes وon وoff والقائمة الكاملة للقيم المنطقية في YAML 1.1) ويتيح لك اختيار مسافة بادئة مسافتين أو 4 مسافات. للاتجاه المعاكس، يتعامل محوّل YAML إلى JSON مع المراسي والمراجع وYAML متعدد الوثائق. تعمل كلتا الأداتين كلياً في متصفحك — لا تغادر بياناتك جهازك، وهذا مهم عند العمل مع بيانات Kubernetes الإنتاجية أو خطط Terraform التي تحتوي على إعدادات موارد حساسة.