محوّل JSON إلى YAML

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

الإدخال
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 صالح ومعناهما واحد؛ غير أن المُزاحة هي ما يكتبه أكثر الناس وما تطويه أكثر المحرّرات طيًّا صحيحًا.

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

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

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

أهو 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 ورجوعًا، فكل تعليق فيه قد زال.
أيُرسَل شيء مما ألصقه إلى خادم؟
لا. التحليل والتحويل يجريان كلاهما داخل متصفحك؛ ولا شيء يُرفَع أو يُسجَّل، وهو يعمل بلا اتصال بالشبكة.