محوّل JSON إلى YAML

يحوّل JSON إلى YAML لملفات Kubernetes وDocker Compose، ويشرح كل اقتباس يضيفه — أي السلاسل التي كانت لتصير في صمت قيمة منطقية أو عددًا أو تاريخًا من دونه.

الإدخال
YAML
name: deploy
country: 'NO'
startsAt: '12:30'
mode: '0755'
version: '1.10'
released: '2024-01-30'
enabled: 'yes'
script: |-
  set -e
  npm run build
  npm test
replicas: 3
tags:
  - web
  - edge
لماذا اقتُبست كل سلسلة

القيم المقتبسة: ‎6. مطلوبة في YAML 1.1 وحده: ‎4. بدّل المخطط أعلاه لترى الفرق.

  • country1.1 فقط

    «‎NO» كانت لتُقرأ القيمة المنطقية ‎false.

  • startsAt1.1 فقط

    «‎12:30» كانت لتُقرأ في الأساس الستيني ‎750.

  • mode

    في «‎0755» صفر بادئ، فكانت لتُقرأ العدد ‎493.

  • version

    «‎1.10» كانت لتُقرأ العدد ‎1.1.

  • released1.1 فقط

    «‎2024-01-30» كانت لتُقرأ تاريخًا لا نصًا.

  • enabled1.1 فقط

    «‎yes» كانت لتُقرأ القيمة المنطقية ‎true.

ما الذي يفعله هذا الأداة

يحوّل مستند JSON إلى YAML، ثم يخبرك لماذا انتهت كل سلسلة بين علامتَي اقتباس. وهذا الشقّ الثاني هو سبب وجود الأداة: فكل محوّل آخر يعيد إليك المخرَج ويترك لك أن تكتشف لاحقًا أن إحدى قيمك لم تعد سلسلة.

والتحويل يجري في اتجاه واحد. فقراءة YAML مسألة أكبر بكثير من كتابته، ومحلّل YAML الصحيح جزئيًّا أسوأ من لا محلّل: إذ يقبل ملفك ويعيد البيانات الخاطئة من غير شكوى. أما الإخراج فمسألة محدودة، وهو الاتجاه المغطّى هنا.

لماذا يحتاج المحوّل إلى آراء أصلًا

يقول JSON نوع كل شيء. فللسلسلة علامتا اقتباس، وليست للعدد، ولا وجود لاحتمال ثالث. أما YAML فيقرّر نوع القيمة غير المقتبسة بالنظر إلى هيئتها: فإن طابقت نمط القيمة المنطقية فهي منطقية، وإن طابقت عددًا فهي عدد، ولا تبقى نصًا إلا إذا لم تطابق شيئًا.

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

مشكلة النرويج وأقاربها

أشهر الحالات قائمة رموز البلدان. فالنرويج NO، وفي YAML 1.1 يكون الرمز غير المقتبس NO هو القيمة المنطقية false. فيفقد ملف إعدادات يعدّد البلدان النرويجَ ويكسب false، ولا شيء في أي موضع يبلّغ عن خطأ.

وليست قاعدة شاذّة واحدة بل أسرة كاملة. فـ YAML 1.1 يقرأ y و‑Y و‑yes و‑no و‑on و‑off قيمًا منطقية بأي رسم، فيصيب بذلك رمزًا كيميائيًّا وموضع مفتاح وجوابًا عن سؤال. وحلّالات الأعداد أغرب من ذلك:

  • 12:30 هو 750. فـ YAML 1.1 يقرأ الأرقام المفصولة بنقطتين في الأساس الستيني، فتصير ساعة من النهار أو مدة عددًا صحيحًا.
  • 0755 هو 493. فالصفر البادئ يعني الثماني في YAML 1.1 — وفي YAML 1.2 النص نفسه هو العشري 755: فالنسختان تختلفان في أي عدد، لا في كونه عددًا.
  • 1.10 هو 1.1. فرقم الإصدار ذو الجزأين عدد عشري، والصفر الأخير يزول. فالاعتماد المثبَّت على 1.10 صار يشير إلى 1.1.
  • 2024-01-30 كائن تاريخ لا سلسلة، لأن لـ YAML 1.1 نوعًا للطابع الزمني.
  • والسلسلة الفارغة null، وكذلك الكلمات المجرّدة null و‑Null و‑NULL والمدّة.

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

نسختان، ولماذا الأقدم هي الافتراضية

صدر YAML 1.2 سنة 2009 فأزال أكثر الحلّالات إثارةً للمفاجأة. ومخططه الأساسي لا يُبقي منطقيًّا إلا true و‑false، ويسقط الأساس الستيني كلّه، ولا نوع للطابع الزمني فيه. فتحت 1.2 تكون NO و‑12:30 و‑2024-01-30 سلاسل ليس إلا.

والمأزق فيما يقرأ ملفك فعلًا. فـ PyYAML ينفّذ YAML 1.1، وهو المحلّل وراء كمّ هائل من الأدوات — Ansible، وعملاء Kubernetes القدامى، وسكربتات لا تُحصى. أما yaml.v3 في Go و‑js-yaml الحالي فيتبعان 1.2. فيمكن إذن أن يُقرأ المستند نفسه قراءتين مختلفتين بحسب من يفتحه، والمخرَج الوحيد الآمن في كل مكان هو المقتبَس على 1.1.

وهذا هو الافتراضي هنا. وتبديل الإعداد إلى 1.2 لا يخفي شيئًا — إذ يُعاد الإخراج بالحلّالات الأحدث وتنكمش لوحة النتائج، فترى بالضبط أي الاقتباسات كانت هناك من أجل المخطط الأقدم. والباقية منها هي التي يحتاجها كل محلّل.

سلاسل تكسر النحو لا النوع

ثمّة مجموعة ثانية من السلاسل يجب اقتباسها لسبب آخر: لا لأن YAML سيقرؤها نوعًا آخر، بل لأنها لن تُحلَّل نصًا البتة.

  • النقطتان تتبعهما مسافة تُنهيان مفتاحًا. فـ«note: time: now» كانت لتُقرأ مفتاحًا اسمه note قيمته مفتاح اسمه time.
  • والمسافة يتبعها مربّع تبدأ تعليقًا، فيزول كل ما بعدها.
  • والبادئ من - و‑? و‑: و‑‎[‎ و‑‎]‎ و‑‎{‎ و‑‎}‎ و‑# و‑& و‑* و‑! و‑| و‑‎>‎ و‑% و‑@ أو الفاصلة العليا المائلة محرف إشارة ومعناه بنيوي.
  • والمسافة البادئة أو اللاحقة لا تحفظها قيمة غير مقتبسة، فتعود « x » على هيئة «x».
  • والجدولة في أي موضع من القيمة مرفوضة رفضًا تامًّا — إذ يأبى PyYAML المستند كلّه بدل أن يقرأه خطأً، فتفشل هذه الحالة بصوت عالٍ.

وتُستعمل الفاصلة العليا المفردة حيثما كفت، لأن لها قاعدة هروب واحدة بعينها — إذ تُكتب الفاصلة مرتين — ولأنها أيسر قراءةً من هروبات الشرطة المائلة العكسية التي تجرّها الفاصلتان المزدوجتان. أما المزدوجتان فمحفوظتان لما يحتاج الهروب حقًّا: محارف التحكّم، والجدولات، والسلاسل متعددة الأسطر التي لا تستطيع استعمال كتلة.

السلاسل متعددة الأسطر ومؤشر القطع

السلسلة التي فيها أسطر جديدة — سكربت، أو شهادة، أو كتلة نثر — هي في الغالب سبب رغبة المرء في YAML أصلًا. فـ JSON لا يستطيع كتابتها إلا بهروبات الشرطة العكسية و‑n في سطر واحد طويل جدًّا؛ أما YAML فله الكتلة السلمية الحرفية، تفتتحها شرطة قائمة، ويظهر النص تحتها مُزاحًا مقروءًا.

والدقّة في ما يحلّ بالأسطر الجديدة في النهاية، وذلك ما يحكمه مؤشر القطع:

  • الشرطة القائمة المجرّدة تقتصّ: مهما بلغ عدد الأسطر الأخيرة في الكتلة، تنال القيمة سطرًا واحدًا بالضبط.
  • والشرطة القائمة يتبعها ناقص تحذف: فلا تنال القيمة شيئًا منها.
  • والشرطة القائمة يتبعها زائد تحفظ: فتنال القيمة كل واحد منها.

وتختار هذه الأداة المؤشر من السلسلة التي تلقّتها، فتنجو القيمة من رحلة الذهاب والإياب بالضبط. ويستحق ذلك أن يُعرف لأن الافتراضي — الشرطة المجرّدة — هو ما يكتبه الناس بأيديهم، وهو يسوّي في صمت سلسلة انتهت بسطرين جديدين أو بلا سطر.

والكتلة السلمية لا تحمل كل شيء، وحيث لا تحمل يرتدّ المخرَج إلى الفاصلتين المزدوجتين وتقول لوحة النتائج لماذا. فإرجاع العربة لا ينجو، لأن الكتل السلمية تسوّي فواصل الأسطر. والسطر الأول الذي يبدأ بمسافة كان ليُقرأ إزاحةً زائدة ويُنزع. والسطر الذي ينتهي بمسافة تحفظه المواصفة، غير أنه غير مرئي على الشاشة وتحذفه أكثر المحرّرات عند الحفظ، فالاقتباس أسلم — وذلك قرار لا قصور، وهو مُبلَّغ عنه بوصفه كذلك.

أكثر من مستند واحد

لـ YAML شيء ليس لـ JSON: إذ يستطيع الملف أن يحمل تدفّقًا من المستندات تفصلها ثلاث شَرطات. وهذه صيغة بيان Kubernetes، وهي سبب حاجة مصفوفة JSON في كثير من الأحيان إلى أن تصير شيئًا غير تسلسل YAML.

والمفتاح هنا يُخرج كل عنصر من مصفوفة عليا مستندًا مستقلًّا. وحين لا يكون المدخل قيمة JSON واحدة أصلًا بل يُحلَّل كل سطر منه وحده، يُقرأ NDJSON — وهي الصيغة المفصولة بالأسطر التي تصل بها السجلات وصادرات الواجهات — فيصير كل سطر مستندًا. وهذه القراءة تخمين، فيُبلَّغ عنها فوق المخرَج بدل أن تُتّخذ في صمت.

ولا ينطبق هذا الارتداد إلا حين يفشل المدخل كلّه في التحليل وينجح كل سطر منه، وهو ما لن يفعله مستند معطوب فحسب. فيبقى خطأ النحو ظاهرًا خطأَ نحو، ومعه السطر والعمود اللذان وقع فيهما.

ما لا يستطيع التحويل حفظه

يضيع شيئان قبل أن ترى هذه الأداة بياناتك أصلًا، وكلاهما في تحليل JSON نفسه، ويستحق أن يُعرف أيّهما.

المفاتيح المكرَّرة. فـ JSON يجيز للكائن أن يذكر المفتاح نفسه مرتين، وأكثر المحلّلات تبقي الأخير في صمت. أما YAML فيحرّم التكرار تحريمًا، فيكون المخرَج صالحًا، لكن القيمة الأولى قد ذهبت من قبل — ولا يستطيع محوّل أن يبلّغ عمّا لم يتلقّه قط.

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

ملاحظات على المخرَج

الإزاحة بالمسافات دائمًا، لأن YAML يحرّم الجدولات في الإزاحة تحريمًا تامًّا — وهو من الأمور القليلة التي تتشدّد فيها الصيغة. ومسافتان هما العُرف؛ وتُعرض أربع لأن بعض أساليب البيوت البرمجية تريدها.

والتسلسلات مُزاحة تحت مفتاحها. وكلٌّ من هذه الصيغة والصيغة غير المُزاحة YAML صالح ومعناهما واحد؛ غير أن المُزاحة هي ما يكتبه أكثر الناس وما تطويه أكثر المحرّرات طيًّا صحيحًا.

والمصفوفة أو الكائن الفارغ يُكتب بأسلوب التدفّق زوجَ أقواس، لأن أسلوب الكتلة لا سبيل له إلى التعبير عن الفراغ: فلا شيء يُكتب في الأسطر التالية.

وينتهي المخرَج بسطر جديد، وذلك حامل لمعنًى لا زينة. فقطع الكتلة السلمية يُقاس على فاصل السطر الذي يليها، فالكتلة المقتصّة في آخر ملف بلا سطر أخير تفقد السطر الذي كان عليها أن تحفظه.

بيانات Kubernetes والحقول التي يجب أن تبقى نصوصًا

يقرأ Kubernetes البيان بأي من الصيغتين. وتسمّي وثائقه نفسها YAML الصيغة المتعارف عليها وتذكر JSON بديلًا عنها، ويحوّل kubectl البيان إلى JSON، أو إلى صيغة تسلسل أخرى تدعمها الـ API، عند إرسال الطلب — أي أن التحويل ليس عمّا يقبله العنقود، بل عن الملف الذي تحفظه: ‏YAML هو ما يقرأه المراجع، وما يكون فيه الفرق في طلب الدمج مقروءًا، والوحيد من الاثنين القادر على حمل تعليق. وغالبًا ما يأتي JSON من ‎kubectl get -o json أو من قالب أو من واجهة برمجية تعيد الكائنات.

  • ملف واحد وكائنات عدة. يمكن جمع البيانات في ملف واحد تفصلها ثلاث شَرطات، وتقول الوثائق صراحة إنها تُنشأ بالترتيب الذي تظهر به — ولهذا يُكتب Service عادة فوق الـ Deployment الذي يملؤه. والمفتاح الذي يبثّ كل عنصر من مصفوفة عليا مستندًا مستقلًا هو ما يحوّل قائمة كائنات JSON إلى ملف كهذا.
  • مدخلات بيئة الحاوية حقلان من نوع النص. فوثائق الـ API تعرّف كلًّا من name وvalue على أنه نص، ولذلك يجب أن تخرج قيمة JSON التي هي "true" مع علامتي الاقتباس حولها: فبدونهما تصير قيمة منطقية، والقيمة المنطقية ليست النوع الذي عُرّف الحقل ليحمله.
  • منفذ الحاوية معرَّف عددًا صحيحًا، وهو يقع على بعد أسطر قليلة من تلك النصوص. فالاثنان يحتاجان إلى معالجة معاكسة، وJSON يحمل التمييز أصلًا — فالعدد يتحوّل إلى عدد عادي، والنص لا يُقتبس إلا حيث يتوقف YAML عن قراءته نصًا.
  • التسميات والتعليقات التوضيحية خرائط من نص إلى نص. وهناك تحل مشكلة النرويج داخل البيان: فتسمية قيمتها NO أو on أو off هي قيمة منطقية في نظر محلّل YAML 1.1، والقيمة ‎1.10 عدد عشري يفقد الصفر في آخره.

ثلاثة مدخلات بيئة، معطاة بصيغة JSON وكل قيمة فيها نص، تتحوّل إلى هذا:

env:
  - name: DEBUG
    value: 'true'
  - name: REPLICAS
    value: '3'
  - name: COUNTRY
    value: 'NO'

الثلاثة كلها مقتبسة، ولوحة النتائج تسمّي الثلاثة مع القيمة التي كانت كل واحدة ستصير إليها: ‏true قيمة منطقية، و3 عدد، وNO هي القيمة المنطقية false. والأخيرة وحدها مرتبطة بالإصدار — حوّل المخطط إلى 1.2 فتسقط عنها علامتا الاقتباس، لأن 1.2 لا يقرأ NO شيئًا سوى نص. والأخريان تحتفظان بعلامتيهما في الإصدارين.

Docker Compose حيث تحذّر وثائق الصيغة نفسها من YAML

ملف Compose هو YAML، وتحمل وثائق Docker الخاصة به تحذيرين عن تحليل YAML. ووثائق منتج تسمّي صيغة التسلسل التي يستعملها خطرًا أمر غير مألوف، والتحذيران كلاهما عن الشيء نفسه الذي وُجدت هذه الأداة من أجله: قيمة قُصد بها أن تكون نصًا فيقرأها المحلّل شيئًا آخر.

  • المنافذ. تقول الوثائق إن تعيين HOST:CONTAINER ينبغي أن يُكتب دائمًا نصًا مقتبسًا، تجنبًا للتعارض مع العدد العشري في الأساس الستيني في YAML. فبلا اقتباس، ‎22:22 هو العدد الصحيح 1342.
  • قيم البيئة. تطلب الوثائق أن تُحاط الكلمات true وfalse وyes وno بعلامتي اقتباس حتى لا يحوّلها المحلّل. اثنتان من الأربع قيم منطقية في إصداري YAML كليهما، واثنتان — yes وno — في 1.1 وحده، ولهذا تعلّمهما لوحة النتائج بشكل مختلف.
  • وقاعدة الأساس الستيني لا تصل إلا إلى تعيين يكون العدد الذي بعد النقطتين فيه أقل من ستين، لأن هذا هو المدى الذي تغطيه خانة واحدة في الأساس الستيني. ولذلك يُقتبس ‎22:22 هنا ويُترك ‎8080:80 عاديًا، والفرق هو القاعدة لا حكم على أي المنافذ أهم.

خدمة منافذها وقيم بيئتها كلها نصوص في JSON تتحوّل إلى هذا:

services:
  proxy:
    image: nginx
    ports:
      - '22:22'
      - 8080:80
    environment:
      TLS_ENABLED: 'no'
      DEBUG: 'true'

هذا هو المخرج الذي تطلبه الوثائق، والمخطط الافتراضي هو ما ينتجه. وهو أيضًا أوضح سبب لترك المخطط كما هو: حوّل إلى 1.2 فيفقد تعيين المنفذ وno علامتيهما بينما يحتفظ true بعلامتيه، لأن 1.2 تخلّى عن الأساس الستيني ولم يُبقِ قيمًا منطقية إلا true وfalse. والمحلّل الذي ينفّذ 1.2 يقرأ النتيجة قراءة صحيحة — لكن نصيحة Docker مكتوبة بلا أي شرط كهذا، والالتزام بها يكلّف زوجين من علامتي الاقتباس.

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

أهو JSON بالفعل YAML صالح؟
تحت YAML 1.2 نعم: تقول المواصفة ذلك صراحةً، ويقرأ محلّل 1.2 ملف JSON مباشرةً. غير أن ذلك لا ينفع كثيرًا عمليًّا، لأن سبب التحويل هو الوضوح — التعليقات، والكتل السلمية، وغياب الأقواس المعقوفة — ولصق JSON في ملف YAML لا يمنحك شيئًا من ذلك. وتحت YAML 1.1 ليس ذلك صحيحًا تمامًا، وهذا سبب آخر للتمييز بين النسختين.
لماذا نالت سلسلتي اقتباسًا يبدو أنها لا تحتاجه؟
هي تحتاجه على الأرجح الأكيد. فـ YAML يحدّد نوع القيمة غير المقتبسة بمطابقة الأنماط، فتكفّ NO و‑yes و‑off و‑12:30 و‑0755 و‑1.10 و‑2024-01-30 والسلسلة الفارغة كلها عن كونها سلاسل. ولوحة النتائج تسمّي كل واحدة وتعرض القيمة التي كانت ستصير إليها، فيُتحقّق من الدعوى بدل تصديقها. وإن كنت تستهدف محلّل YAML 1.2 فتبديل المخطط يزيل ما لا يحتاجه إلا 1.1.
ما الفرق هنا بين YAML 1.1 و‑1.2؟
أسقط 1.2 الحلّالات التي تسبّب أكثر المفاجآت: فلم تعد yes/no/on/off منطقية، وزال الأساس الستيني، ولا نوع للطابع الزمني. ولأن PyYAML ينفّذ 1.1 ولا يزال في كل مكان، فالمخرَج المحافظ هو الافتراضي؛ وإعداد 1.2 موجود لحين تعلم ما الذي سيقرأ الملف.
أيستطيع تحويل YAML رجوعًا إلى JSON؟
لا، عن قصد. فقارئ YAML يحتاج إلى المراسي والأسماء البديلة والوسوم ومفاتيح الدمج وخمسة أنماط للسلميات ونسختَي مخطط، والخطأ الدقيق في أيّها يعني قبول ملف وإعادة بيانات غير التي احتواها. وهذا الفشل صامت، وهو ما يجعله أسوأ من عدم تقديم الميزة أصلًا.
كيف أحصل على ملف متعدّد المستندات على طريقة Kubernetes؟
شغّل المفتاح الذي يُخرج كل عنصر من مصفوفة عليا مستندًا مستقلًّا، فتصير المصفوفة مستندات تفصلها ثلاث شَرطات. وإن كان مدخلك NDJSON — كائن JSON في كل سطر، كما تصل السجلات وصادرات الواجهات غالبًا — فذلك يُكتشف تلقائيًّا ويُبلَّغ عنه فوق المخرَج.
لماذا لا تعليقات في المخرَج؟
لأنه لم يكن فيه تعليقات في المدخل. فالتعليقات أهم ما لـ YAML وليس لـ JSON، ولا يستطيع محوّل أن يخترعها. ويستحق ذلك أن يُتذكَّر في الاتجاه الآخر أيضًا: فإن أمررت ملف YAML عبر JSON ورجوعًا، فكل تعليق فيه قد زال.
أيُرسَل شيء مما ألصقه إلى خادم؟
لا. التحليل والتحويل يجريان كلاهما داخل متصفحك؛ ولا شيء يُرفَع أو يُسجَّل، وهو يعمل بلا اتصال بالشبكة.
كيف أحوّل مخرَج kubectl بصيغة JSON إلى بيان YAML؟
الصقه واقرأ YAML. يقبل Kubernetes الصيغتين — وتسمّي وثائقه YAML الصيغة المتعارف عليها وJSON البديل — فسبب التحويل هو الملف الذي تحفظه لا ما يقبله العنقود. والذي يجب فحصه هو علامات الاقتباس: فالبيان مملوء بحقول تعرّفها الـ API على أنها نصوص، ومنها قيم البيئة وقيم التسميات والتعليقات التوضيحية، وهي بالضبط ما كان YAML سيغيّر نوعه. وإن كان JSON قائمة كائنات عدة، فمفتاح المستندات المتعددة يحوّلها إلى ملف واحد.
لماذا يريد Docker Compose منافذي بين علامتي اقتباس؟
لأن ‎22:22 ليس زوج أعداد في نظر محلّل YAML 1.1، بل عدد واحد في الأساس الستيني — 1342. تقول وثائق Compose من Docker إن تعيين HOST:CONTAINER ينبغي أن يكون دائمًا نصًا مقتبسًا لهذا السبب بعينه، وتطلب الصفحة نفسها أن تُقتبس true وfalse وyes وno داخل كتلة البيئة. والأمران كلاهما ما تفعله هذه الأداة تحت مخططها الافتراضي، ولوحة النتائج تقول من أي قاعدة جاءت كل علامة. أما التعيين الذي يكون العدد بعد النقطتين فيه ستين أو أكثر، مثل ‎8080:80، فهو خارج قاعدة الأساس الستيني ويُترك عاديًا.
أي إصدار من YAML أختار لـ Kubernetes أو Docker Compose؟
الافتراضي، 1.1. فكل علامة اقتباس يضيفها يقبلها محلّل 1.2 أيضًا، فالمخرج المحافظ آمن في الحالتين، وتحذيرا وثائق Compose كلاهما عن قواعد 1.1 — تعيينات المنافذ في الأساس الستيني، وyes وno كقيمتين منطقيتين. وإعداد 1.2 موجود ليُظهر أي علامات الاقتباس لا توجد إلا من أجل المخطط الأقدم؛ فالمخرج يفقدها، وهذا نقيض ما يطلبه التحذيران.

أدوات ذات صلة