JSON Toolkit

تحويل JSON إلى YAML من سطر الأوامر — أربعة مزالق وراء أمر yq واحد

الخلاصة: أكثر الأوامر تكرارًا في الشروحات، yq -P input.json > output.yaml، ما زال يُخرج JSON على yq v4. يقيس هذا المقال أداء yq v4.44.6 وPyYAML 6.0.3 وjs-yaml على عيّنة بحجم 2.39 MB، ويشرح لماذا يحدد امتداد الملف صيغة الإخراج، ثم يعطي الصيغ الصحيحة للتحويل الدفعي والاقتباس والمسافات البادئة ودقة الأعداد الصحيحة الكبيرة.

متوسطقراءة نحو 9 دقيقةنُشر في 2026-11-03حُدِّث في 2026-11-03
غلاف يقارن أمر تحويل JSON إلى YAML وناتجه داخل الطرفية

الخلاصة: أكثر الأوامر نسخًا ولصقًا في الشروحات,yq -P input.json > output.yaml,يُنتج JSON لا YAML على إصدار yq v4. للحصول على YAML يجب تمرير -o=yaml (أو -oy) صراحةً. أمّا -P فليس إلا اختصارًا لـ --prettyPrint: إنه يتحكّم في المسافات البادئة وأسلوب الكتلة، ولا يتحكّم في صيغة الإخراج.

لم يكن تحويل JSON إلى YAML يومًا مسألة «تغيير امتداد الملف». التكلفة الحقيقية تكمن في أربعة مواضع: امتداد الدخل يحدّد صيغة الإخراج، وقواعد الاقتباس تُعيد كتابة نصوصك بصمت، ومسافات البادئة في المتتاليات تؤثر في الأدوات اللاحقة، والعدد الصحيح الكبير الذي يمرّ عبر yq يفقد دقته إلى الأبد.

1. شغّل الأمر الأكثر شيوعًا مرة واحدة

على جهاز نظيف ثُبّت عليه yq v4.44.6، أنشئ أصغر مدخل ممكن:

printf '{"a":1,"b":{"c":2}}' > s.json
yq -P '.' s.json

ما يعود إليك ليس YAML، بل JSON بمسافات بادئة:

{
  "a": 1,
  "b": {
    "c": 2
  }
}

إن اتبعت الشرح وأعدت توجيه هذا الأمر إلى output.yaml، ستحصل على ملف امتداده .yaml ومحتواه JSON. كل محلّل YAML 1.2 يقرأه بلا تردّد (فـ JSON مجموعة فرعية من YAML 1.2، والاستدلال الكامل لهذا الاحتواء مبسوط في حدود التحويل بين JSON وYAML وXML)، لذلك لن يظهر أي خطأ في البداية. ينكسر الأمر في اليوم الذي يضيف فيه أحدهم تعليقًا أو مرساة، أو حين يبدأ yamllint داخل CI بإغراقك بسيل من truthy value should be one of، فتبدأ رحلة البحث عن سبب لا يخطر على بال أحد لأنّ اسم الملف يوحي بغير الحقيقة.

الصياغة التي تُنتج YAML فعلًا تسمّي صيغة الإخراج صراحةً:

yq -o=yaml '.' s.json
# a: 1
# b:
#   c: 2

الفرق بين الأمرين ليس في التعبير '.' ولا في اسم الملف، بل في الراية الوحيدة -o. كثير من الشروحات كُتبت في زمن yq v3 الذي كان يفترض YAML افتراضيًا، أو كُتبت لأشخاص يغذّون البيانات عبر الأنبوب، فلا يلمسون فرع الامتداد أصلًا. حين تنسخ الأمر كما هو إلى ملف s.json حقيقي، تظهر المفارقة كاملة: الملف المسمّى output.yaml يبدأ بـ { وينتهي بـ }، وأي أداة تفترض أنّ «هذا YAML مكتوب بأسلوب الكتلة ويحمل تعليقات» ستفاجأ بما تجده. ولهذا نبدأ بالمثال لا بالنظرية؛ فمن يرى الناتج بعينيه لا يعود يثق بالأمر المنسوخ.

2. لماذا يحدث هذا: الامتداد يحدّد صيغة الإخراج

يتبع yq v4 قاعدة «صيغة الدخل تحدّد صيغة الإخراج». فهو يستنتج الصيغة من امتداد ملف الدخل، وحين لا تمرّر -o يعيد استخدام ذلك الاستنتاج كما هو: دخل .json → إخراج JSON، ودخل .yaml → إخراج YAML، والقراءة من stdin بلا امتداد تفترض YAML افتراضيًا. أربع صياغات على المدخل نفسه، مقيسة عمليًا:

الأمر مصدر الدخل صيغة الإخراج الفعلية الحكم
yq -P '.' s.json ملف s.json JSON (بمسافات بادئة) فخ عند التوجيه إلى .yaml
yq -o=yaml '.' s.json ملف s.json YAML (أسلوب الكتلة) صحيح
yq -P -o=yaml '.' s.json ملف s.json YAML (أسلوب الكتلة) صحيح، و-P زائد هنا
cat s.json | yq -P '.' stdin بلا امتداد YAML (أسلوب الكتلة) صحيح، لأن YAML هو الافتراضي

الصف الثالث يقول إنّ -P ليس ضروريًا دائمًا متى وُجد -o=yaml، لكنه ليس بريئًا أيضا — انظر أرقام الزمن في القسم السادس. أمّا الصف الرابع فيفسّر لماذا يبدو «نسخت الشرح فنجح» مقنعًا إلى هذا الحدّ: كثيرون يغذّون البيانات عبر الأنبوب، فيلتفّون على فرع الامتداد برمّته دون أن يدروا، ويظنّون أنّ -P وحده هو الذي صنع YAML، مع أنّ الذي صنعه هو غياب الامتداد.

لهذه القاعدة تبعات مباشرة على السكربتات التي تُعيد تسمية الملفات. لو حوّلت s.json إلى s.yaml قبل استدعاء yq، لتغيّر الاستنتاج وحده ولصار الإخراج YAML حتى بلا -o. وبالعكس، لو قرأت ملف .yaml وأردت JSON، فستحتاج -o=json. والاعتماد على الامتداد سلوك صامت تمامًا: لا رسالة تحذير، ولا سطر في السجلّ يخبرك بأنّ yq بدّل الصيغة من تحت قدميك. لهذا نفضّل في كل ما يلي كتابة -o=yaml صراحةً حتى حين يكون الافتراضي في صالحنا؛ فالصريح يبقى صالحًا بعد نقل السكربت إلى بيئة أخرى، أو بعد تغيير اسم الملف، أو بعد ترقية الأداة. إن كان هناك درس واحد يُستخلص من هذا القسم فهو: لا تجعل اسم ملف يقرّر معنى بياناتك نيابة عنك.

3. التحويل الدفعي: ثلاث صيغ shell

تحويل ملف واحد ليس إلا نقطة البداية؛ فكل سبب حقيقي تقريبًا يدفعك إلى سطر الأوامر هو سبب دفعي: كل ملفات الإعداد في مجلد، وكل لقطات استجابات API الملتقطة، وكل مخرجات الاختبارات، دفعة واحدة بدل فتح أداة رسومية لكل ملف على حدة.

حلقة bash / zsh (الأوضح، وتكفي لمجلد واحد):

for f in configs/*.json; do
  yq -o=yaml '.' "$f" > "${f%.json}.yaml"
done

find بشكل متكرّر (حين تهمّ المجلدات الفرعية أيضًا):

find . -name '*.json' -exec sh -c 'yq -o=yaml "." "$1" > "${1%.json}.yaml"' _ {} \;

PowerShell (على Windows CI):

Get-ChildItem *.json | ForEach-Object { yq -o=yaml '.' $_.FullName > ($_.BaseName + '.yaml') }

تشترك الصيغ الثلاث في شرط صارم واحد: تحقّق من صحة JSON المصدر أولًا. فالمحوّل ليس محلّلًا؛ وأمام خطأ نحوي تُخرج أدوات كثيرة موضعًا مبهمًا بدل التفصيل على مستوى السطر الذي يعطيه المدقّق المتخصّص. إضافة حارس واحد داخل الحلقة تكلف شبه معدومة:

for f in configs/*.json; do
  jq empty "$f" || { echo "invalid: $f"; continue; }
  yq -o=yaml '.' "$f" > "${f%.json}.yaml"
done

jq empty يحلّل بلا طباعة ويخرج برمز خروج غير صفري على JSON غير صالح. ومع set -euo pipefail يتوقّف خط الأنابيب بأكمله عند أول ملف تالف، بدل أن يُنتج كومة من المخرجات نصف المحوّلة التي تبدو سليمة للعين المجردة.

نقطة عملية أخرى تستحق التنبيه: إعادة التوجيه > تُنشئ ملف الإخراج قبل أن يبدأ yq عمله، فإن فشل التحويل في المنتصف بقي ملف .yaml فارغًا أو ناقصًا في المجلد، وقد تلتقطه أداة لاحقة وتعامله كأنه صالح. في السكربتات الحسّاسة اكتب إلى اسم مؤقت ولا تُعد التسمية إلا بعد نجاح الأمر، أو اترك set -e يوقف كل شيء قبل أي نقل. والخلاصة أنّ التحقق المسبق أرخص بكثير من تنظيف مئات الملفات نصف المكتوبة بعد فوات الأوان، خصوصًا حين يكون المجلد جزءًا من مستودع تُدمج تغييراته آليًا.

4. أربع رايات إخراج، وأربع مهمّات منفصلة

قائمة رايات yq طويلة، لكن أربعًا فقط تهمّ على مسار JSON → YAML هذا:

الراية ما تفعله متى يجب تمريرها
-o=yaml / -oy يضبط صيغة الإخراج على YAML كلما كان الدخل ملف .json
-P / --prettyPrint يفرض أسلوب الكتلة ويضبط المسافات البادئة القراءة من stdin، أو حين تهمّ مسافة بادئة المتتاليات
-I=n عرض المسافة البادئة، والافتراضي 2 حين يطلب المستهلك 4 مسافات (بعض أدلة أسلوب Ansible)
--split-exp يقسّم عناصر المصفوفة إلى مستندات عند توليد مستندات Kubernetes متعددة الموارد

مسألة المستندات المتعددة تستحق وقفة خاصة. جرت العادة في ملفات Kubernetes على فصل الموارد بـ ---، وتوليد ذلك من مصفوفة JSON يتم هكذا:

yq -P -o=yaml '.[] | splitDoc' --split-exp 'true' kubernetes-list.json

بلا --split-exp تحصل على متتالية YAML واحدة؛ ومعه يصبح كل عنصر من المصفوفة مستندًا مستقلًا يفصله ---. كلا الملفين يقبلهما kubectl apply، لكنّ الفرق في قابلية قراءة الـ diff يبلغ مرتبة كاملة.

عمليًا، يظهر هذا الفرق أول ما يظهر في مراجعة الكود: المتتالية الواحدة تعني أنّ تعديل مورد واحد يُنتج diff يحيط بكل ما حوله، بينما المستندات المنفصلة تُظهر كل مورد في موضعه ولا تُزعج البقية. ولهذا نُدرج --split-exp في سكربتات التوليد لا في خطوات العرض اليدوية؛ فالأخيرة قد تُربك من يقرأ YAML لأول مرة حين يرى --- في منتصف الملف ويظنّه خطأً نحويًا. أمّا -I=n فقيمته تظهر حين يفرض دليل أسلوب الفريق عرضًا غير الافتراضي، ولا شيء يكشف تجاهله أسرع من yamllint وهو يرفض ملفًا صحيحًا دلاليًا لمجرّد اختلاف المسافات.

5. الاقتباس: ستة أشكال من النصوص يجب اقتباسها

استنتاج الأنواع في YAML ضمني؛ فأي نص يشبه عددًا أو قيمة منطقية يُقرأ على أنه ذلك النوع ما لم يُقتبس. وليس هذا خطرًا نظريًا: بالقياس على yq v4.44.6 تُقتبس القيم الست التالية تلقائيًا، وهذا في ذاته دليل على أنّ المحلّل كان ليُخطئ قراءتها فعلًا:

printf '{"version":"1.0","enabled":"true","port":"8080","empty":"","colon":"host: value","hash":"v # c","unicode":"端口 8080","ts":"2026-11-03"}' | yq -P -o=yaml '.'
version: "1.0"
enabled: "true"
port: "8080"
empty: ""
colon: 'host: value'
hash: 'v # c'
unicode: 端口 8080
ts: "2026-11-03"

العواقب، واحدة بواحدة:

القيمة الأصلية (نص JSON) تُقرأ بلا اقتباس كـ الأثر الفعلي
"1.0" عدد عشري 1.0 يصبح رقم الإصدار عددًا عشريًا، و1.10 ينكمش إلى 1.1
"true" قيمة منطقية true ينقلب منطق الشرط أو تتباين الأنواع
"8080" عدد صحيح 8080 يتغيّر نوع المنفذ وتفشل مقارنة النصوص
"" null يختفي النص الفارغ ويحصل المستهلك على None
"host: value" خطأ تحليل أو تعيين متداخل يجب أن تلي النقطتين مسافة، وإلا فخطأ نحوي
"2026-11-03" كائن تاريخ يُقرأ كـ datetime.date لا كنص

الصف الأخير أخبثها. فالتاريخ في JSON مجرد نص؛ وبعد التحويل يصبح تاريخ YAML، وحين يُعاد إلى JSON تُخرج تطبيقات كثيرة كائن تاريخ بصيغة ISO بدل النص الأصلي، فينكسر اتساق الرحلة ذهابًا وعودة. وكلٌّ من PyYAML وyq يختار إضافة علامات الاقتباس هنا، وهذا إقرار ضمني بأنّ الخطر حقيقي لا افتراضي.

والقاعدة الأيسر من حفظ قائمة الاستثناءات غيبًا: إن كان الحقل نصًا بحكم معناه في مخططك — رقم إصدار، رمز بريدي، معرّف، تاريخ، منفذ — فاقتبسه في JSON قبل التحويل، ولا تتكل على ذكاء المُسلسِل. صحيح أنّ yq وPyYAML يتفقان على معظم الحالات، لكنهما لا يتفقان على كلها؛ والاعتماد على سلوك أداة بعينها يجعل مخرجك مرتبطًا بإصدارها، فتغيير yq v4.44.6 إلى إصدار آخر قد يبدّل نمط الاقتباس من " إلى ' أو العكس، وهو تغيير يظهر في كل diff بلا أي سبب منطقي يبرّره.

ولاحظ أيضًا أنّ السطر unicode: 端口 8080 خرج بلا اقتباس — فالنص الصيني آمن في YAML، وليس هو ما ينبغي أن يقلقك. ما ينبغي أن يقلقك هو ترتيب المفاتيح والدقة، وهو ما يتناوله القسمان التاليان.

6. ما يعادل ذلك في Python وNode، مع أزمنة مقيسة

لا يمكنك تثبيت yq في كل مكان: أجهزة معزولة عن الشبكة، وصور CI مقيّدة بلا صلاحيات تثبيت، وWindows بلا مدير حزم. ولكلٍّ من Python وNode صيغة مكافئة، ولكلٍّ منهما افتراضاته التي تنتظر أن تلدغك. أمّا المقارنة بين المسارات الثلاثة بحسب السيناريو فتجدها في أدوات JSON في سطر الأوامر واختيار المحرّر؛ وهذا المقال يبقى مركّزًا على اتجاه JSON → YAML وحده.

Python + PyYAML (ثلاثة معاملات، لا غنى عن أيٍّ منها):

import json, yaml

with open("config.json", encoding="utf-8") as f:
    data = json.load(f)

with open("config.yaml", "w", encoding="utf-8") as f:
    yaml.safe_dump(
        data, f,
        default_flow_style=False,  # أسلوب الكتلة، لا {a: 1, b: 2}
        allow_unicode=True,        # إبقاء الصينية كما هي، لا \u7AEF\u53E3
        sort_keys=False,           # الحفاظ على ترتيب مفاتيح JSON الأصلي
    )

هذه المعاملات الثلاثة افتراضها False / False / True، أي أنّ PyYAML الجاهز من علبته يرتّب المفاتيح أبجديًا ويحوّل الصينية إلى \uXXXX. على المدخل نفسه وبالافتراضات كانت النتيجة:

apiVersion: v1
data:
  LOG_LEVEL: info
  colon: 'host: value'
  unicode: "\u7AEF\u53E3 8080"
kind: ConfigMap
metadata:
  labels:
    app: web
  name: app-config

لاحظ أنّ kind جاء بعد apiVersion وقبل metadata — وعُرف مجتمع Kubernetes أنّ apiVersion يسبق kind؛ واضطراب الترتيب لا يكسر التحليل، لكنه يجعل المراجعة مرهقة. والأهم أنّ هذا الاضطراب لا يظهر في الاختبارات الوظيفية: kubectl apply يقبل الملف، والاختبارات التي تقارن الكائنات بعد التحليل تمرّ، لأنّ JSON وYAML لا يمنحان ترتيب المفاتيح معنى دلاليًا. الذي يتضرّر فعلًا هو الإنسان الذي يراجع diff، والقوالب التي تفترض موضعًا ثابتًا، وأدوات المقارنة النصية التي تُبلّغ عن تغيير في كل سطر لمجرّد إعادة الترتيب. لهذا نعتبر sort_keys=False جزءًا من تعريف «التحويل الصحيح» لا تحسينًا جماليًا يُؤجَّل.

Node + js-yaml:

const fs = require("fs");
const yaml = require("js-yaml");

const data = JSON.parse(fs.readFileSync("config.json", "utf8"));
fs.writeFileSync(
  "config.yaml",
  yaml.dump(data, { lineWidth: -1, noRefs: true, sortKeys: false })
);

lineWidth: -1 يعطّل لفّ الأسطر (وإلا انكسرت الروابط الطويلة في منتصفها)، وnoRefs: true يمنع توليد المراسي والأسماء المستعارة — وهذا لازم حين يكون المستهلك kubectl أو أداة أخرى لا تحلّها. أمّا sortKeys: false فيؤدي الوظيفة نفسها التي يؤديها sort_keys=False في PyYAML، ويذكّر بأنّ المشكلة ليست في لغة بعينها بل في افتراض «الترتيب الأبجدي أنظف» الذي تتبنّاه مكتبات كثيرة. كذلك ليس noRefs: true تجميلًا: ملف YAML يحتوي &anchor و*alias قد يُقرأ على نحو صحيح في المتصفح ثم يفشل في أداة قديمة داخل خط أنابيب النشر، لأنّ توسيع المراسي لا يُدعم بالتساوي في كل التطبيقات.

الأزمنة المقيسة (العيّنة: JSON بحجم 2.39 MB و12000 سجلًا، كل سجل يحتوي كائنًا متداخلًا ومصفوفتين؛ والجهاز هو بيئة الحماية المستخدمة لهذه المهمة):

التنفيذ زمن التحويل زمن إعادة التحليل اتساق الرحلة ذهابًا وعودة
yq -o=yaml v4.44.6 0.45 s 0.45 s متطابق
yq -P -o=yaml v4.44.6 4.16 s — متطابق
Node js-yaml 4.x 0.23 s 0.11 s متطابق
Python PyYAML 6.0.3 3.26 s 8.01 s متطابق

نتيجتان تخالفان الحدس. الأولى: إضافة -P تُبطئ yq بنحو 9 أضعاف (0.45 s → 4.16 s) مع أنّ المخرج متطابق بايت ببايت على هذه العيّنة (2,326,895 بايت في الحالتين) — فـ -P يجري مرحلة إعادة تنسيق كاملة، وتتراكم تكلفتها عند تحويل بضعة مئات من الملفات، فاستغنِ عنها حيث أمكن. الثانية: إعادة تحليل PyYAML (8.01 s) أبطأ بأكثر من الضعف من تسلسله الأمامي (3.26 s)، فإن كان خط أنابيبك يقرأ YAML مرارًا وتكرارًا فليس Python بالخيار الأنسب.

وخلاصة أخرى: 2.39 MB من JSON صار 2.33 MB من YAML بأسلوب الكتلة، أي أصغر بنحو 3%. فالاعتقاد الشائع «YAML أكبر من JSON دائمًا» لا يصمد — ذلك أنّ علامات الاقتباس والفواصل والأقواس المعقوصة في JSON قد يتجاوز حجمها، عند التداخل العميق، حجم مساحات البادئة البيضاء في YAML. وهذه الأرقام تهمّ تحديدًا حين يكون التحويل جزءًا من بناء يتكرّر عشرات المرات يوميًا: فارق الزمن بين -P وبدونه، مضروبًا في مئات الملفات، يحوّل خطوةً كانت تمرّ في ثوانٍ إلى دقائق تنتظرها كل مراجعة. أمّا من يعمل على ملف إعداد واحد لا يتكرّر، فالفارق لا يُذكر، وحينها تكون قابلية القراءة أهم من الزمن. اختر إذن بحسب عدد مرات التشغيل لا بحسب حجم الملف وحده.

7. ثلاثة أشياء لا تعود بعد التحويل

تحدّثت الأقسام السابقة عن «كيف تُحوّل على نحو صحيح»، أمّا هذا القسم فعن «ما الذي يُفقَد إلى غير رجعة». بمدخل مصمَّم خصيصًا لهذا الغرض، ومقيس عمليًا:

printf '{"order_id":12345678901234567890,"zip":"0150","ts":"2026-11-03","f":1.0e6}' > t.json
yq -o=yaml '.' t.json

يُخرج yq:

order_id: 1.2345678901234567e+19
zip: "0150"
ts: "2026-11-03"
f: 1000000

وعند العودة إلى JSON: {"order_id":12345678901234567000,"zip":"0150","ts":"2026-11-03","f":1000000}.

تحوّل رقم الطلب ذو 20 رقمًا 12345678901234567890 إلى 12345678901234567000، فصُفّرت آخر ثلاث خانات بلا أي تحذير على الإطلاق. والسبب أنّ yq يسلك مسار float64 في Go؛ والأعداد الصحيحة التي تتجاوز 2^53 لا يمكن تمثيلها بدقة، فيلجأ إلى التدوين العلمي، وحين تعود تكون القيمة الأصلية قد ذهبت نهائيًا.

المدخل نفسه عبر PyYAML:

order_id: 12345678901234567890
zip: '0150'
ts: '2026-11-03'
f: 1000000.0

أعداد Python الصحيحة عشوائية الدقة، فتبقى الخانات العشرون كاملة وتعود سليمة كما كانت. وإن كان في بياناتك أرقام طلبات أو معرّفات snowflake أو أرقام هويّة طويلة، فلا تستخدم yq — استخدم Python، أو خزّن الحقل نصًا من البداية. والآلية الكاملة وراء هذا النوع من الفشل مشروحة في لماذا تفقد الأعداد الصحيحة الكبيرة دقتها في JSON.

الفقد الثاني هو التمييز بين float وint: يصبح 1.0e6 في إخراج yq هو 1000000 (عدد صحيح)، بينما يُبقيه PyYAML على 1000000.0. والفقد الثالث هو ترتيب المفاتيح — فـ PyYAML يعيد الترتيب افتراضيًا، وyq وjs-yaml يحافظان عليه افتراضيًا، وهذا مصدر خلاف خفيّ في الفرق التي تتعاون بلغات متعددة على الملف نفسه.

وهذه الخسائر الثلاث تتشارك في صفة مزعجة: لا يظهر أيٌّ منها كخطأ. رمز الخروج صفري، والملف الناتج YAML صالح، وأيّ محلّل يقرأه بلا شكوى؛ الذي تغيّر هو المعنى وحده. لذلك لا يكفي أن تختبر «هل نجح التحويل؟»، بل يجب أن تختبر «هل عادت القيم كما كانت؟» عبر مقارنة حقل بحقل بين JSON الأصلي والنسخة العائدة من رحلة JSON → YAML → JSON. وأدرج في مجموعة العينات أرقامًا أطول من 2^53، ونصوصًا تبدأ بصفر مثل "0150"، وتواريخ، وقيمًا عشرية، ونصوصًا فارغة؛ فهذه بالضبط هي الحالات التي تكشف الفروق بين الأدوات قبل أن يكشفها المستخدم النهائي بأثر رجعي مكلف.

8. في CI لا تكتفِ بالتحويل: تحقّق → حوّل → دقّق

اعتبار التحويل الخطوة الوحيدة هو أكثر عيوب خط الأنابيب شيوعًا. فرمز الخروج الصفري يعني «JSON الدخل قد حُلّل»، ولا يعني «المخرج صحيح» ولا «المخرج يفي بمتطلبات المستهلك». والشكل الصحيح من ثلاث مراحل:

#!/usr/bin/env bash
set -euo pipefail

# 1. التحقق من المصدر: الطبقة النحوية
jq empty deployment.json

# 2. التحويل: تسمية صيغة الإخراج صراحةً
yq -o=yaml '.' deployment.json > deployment.yaml

# 3. التحقق من الناتج: YAML قابل للقراءة ونظيف أمام المدقق
yq -o=json -I=0 '.' deployment.yaml > /dev/null
yamllint deployment.yaml

ذلك السطر yq -o=json ... > /dev/null في المرحلة 3 يبدو زائدًا للوهلة الأولى، وهو أنفع سطر في المقطع بأكمله: إنه يُجبر الناتج على أن يُحلَّل من جديد. فأخطاء المسافات البادئة، وفشل حلّ المراسي، والأنواع التي أُعيدت كتابتها تظهر هنا بالضبط، لا في مكان لاحق. وإن تخطّيتها بقيت المشكلة مؤجَّلة إلى kubectl apply أو إلى لحظة إقلاع التطبيق، حيث يكون تتبّعها أغلى بمرات.

وإن كان الناتج إعدادًا يصونه البشر (Helm values، docker-compose، وسير عمل GitHub Actions)، فتحقّق من ثلاثة أمور إضافية: هل اضطرب ترتيب المفاتيح، وهل فقدت الأعداد الطويلة خاناتها، وهل تحتاج التعليقات إلى إضافة يدوية — فـ JSON لا يحمل تعليقات، وأيّ annotation لا يمكن أن يُكتب إلا بعد التحويل، ولن يخترعه أي محوّل نيابة عنك. ويُستحسن أن تُرفق بهذه المراحل فحصًا رابعًا خفيفًا كلما كان الناتج ملفًا يُنسَخ إلى مستودع: قارن المخرج بالنسخة المولَّدة سابقًا، فإن اختلف بلا أي تغيير في المصدر فاحتمال أن يكون الفرق عائدًا إلى ترتيب مفاتيح أو نمط اقتباس أكبر من احتمال أن يكون تغييرًا مقصودًا. هكذا تتحوّل المزالق الأربعة من مفاجآت تُكتشف في الإنتاج إلى شروط تُفحص آليًا قبل كل دمج.

الأسئلة الشائعة

هل أكتب yq -P أم -o=yaml؟

-o=yaml يقرّر «هل المخرج YAML من الأساس»، و-P يقرّر «كيف يبدو هذا YAML». ومع دخل .json يكون -o=yaml إلزاميًا، ولا تحتاج -P إلا حين تريد فرض أسلوب الكتلة أو ضبط مسافة بادئة المتتاليات. أمّا القراءة من stdin بلا امتداد فـ yq يفترض YAML أصلًا، وحينها يكفي -P وحده، لأنّ فرع الامتداد لم يُستدعَ قط.

هل أُمرّر YAML المُحوَّل مباشرة إلى Kubernetes؟

نحويًا نعم، لكن بثلاثة شروط: أن تكون المفاتيح ذات عرف الترتيب مثل apiVersion لم يُعَد ترتيبها (فـ PyYAML يعيد الترتيب افتراضيًا، فاضبط sort_keys=False)، وألا تكون قد تولّدت مراسٍ أو أسماء مستعارة (في js-yaml اضبط noRefs: true)، وأن تكون الأعداد الطويلة قد حافظت على خاناتها كاملة. ولملفات الموارد المتعددة، اقسمها إلى مستندات تفصلها --- عبر --split-exp، وإلا حصلت على متتالية واحدة يصعب مراجعتها.

لماذا خرج YAML في سطر واحد {a: 1, b: 2} بدل أن يكون بمسافات بادئة؟

هذا أسلوب التدفق، وهو YAML صالح لكنه يُفرغ التحويل من معناه، إذ يساوي عمليًا «لم تُحوّل شيئًا». استخدم -P لفرض أسلوب الكتلة؛ ومع PyYAML تأكّد من default_flow_style=False (وقد صار افتراضه False منذ PyYAML 5.1، أمّا الإصدارات الأقدم فتحتاج كتابته صراحةً).

فقد رقم الطلب ذو 20 رقمًا آخر خاناته بعد التحويل إلى YAML، فهل أستعيده؟

لا، وإنما تمنعه مسبقًا. فـ yq يحلّل عبر float64، وكل عدد صحيح يتجاوز 2^53 يفقد دقته بحكم البناء نفسه، بلا استثناء ولا تحذير. فإمّا أن تنتقل إلى Python (أعداد صحيحة عشوائية الدقة، ثبت عمليًا أنها بلا فقد)، وإمّا أن تخزّن الأعداد الطويلة نصوصًا في JSON منذ البداية، وهذا الخيار أوثق لأنه يحلّ في الوقت ذاته مشكلة JSON.parse المماثلة على الجانب JavaScript.

أداة تحويل عبر الإنترنت أم سطر الأوامر؟

للإعدادات التي تحمل أسرارًا ولقطات API التي تحتوي token، استخدم سطر الأوامر أو أداة تعمل في المتصفح وحده، ولا ترفعها إلى أي خادم. وللمهمّات لمرة واحدة صغيرة الحجم حين تريد تأكيدًا بصريًا، تكون الأداة عبر الإنترنت أسرع؛ وصفحة /convert/yaml في هذا الموقع تعمل كليًا داخل متصفحك ولا تخرج البيانات من جهازك أبدًا. أمّا كل ما هو دفعي، أو داخل CI، أو يجب أن يكون قابلًا لإعادة التوليد بأثر قابل للتتبّع، فاستخدم سطر الأوامر واكتب خطوات التحقق في السكربت نفسه لا في ذاكرة من يشغّله.