ממיר JSON ל־YAML
ממיר JSON ל־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. החליפו את הסכמה למעלה כדי לראות את ההבדל.
countryרק 1.1«NO» היה נקרא כבוליאני false.
startsAtרק 1.1«12:30» היה נקרא בבסיס 60 כ־750.
modeל-«0755» יש אפס מוביל, ולכן הוא היה נקרא כמספר 493.
version«1.10» היה נקרא כמספר 1.1.
releasedרק 1.1«2024-01-30» היה נקרא כתאריך ולא כטקסט.
enabledרק 1.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 קורא ספרות מופרדות בנקודתיים בבסיס 60, ולכן שעה ביום או משך זמן הופכים לשלם.
- 0755 הוא 493. אפס מוביל פירושו אוקטלי ב־YAML 1.1 — ובגרסה 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 כבוליאנים, מוותרת על בסיס 60 לגמרי, ואין לה טיפוס חותמת זמן. תחת 1.2, NO ו־12:30 ו־2024-01-30 כולם פשוט מחרוזות.
המלכוד הוא מה באמת קורא את הקובץ שלכם. PyYAML מממש את YAML 1.1, ו־PyYAML הוא המנתח שמאחורי כמות עצומה של כלים — 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 — הפורמט מופרד-השורות שיומנים ויצואי API מגיעים בו — וכל שורה הופכת למסמך. הקריאה הזאת היא ניחוש, ולכן היא מדווחת מעל הפלט ולא נעשית בשקט.
הנפילה חלה רק כשכל הקלט נכשל בניתוח וכל שורה מצליחה, מה שמסמך פגום סתם לא יעשה. שגיאת תחביר עדיין עולה כשגיאת תחביר, עם השורה והעמודה שבהן אירעה.
מה שההמרה אינה יכולה לשמר
שני דברים אובדים לפני שהכלי הזה רואה את הנתונים שלכם, שניהם בניתוח ה־JSON עצמו, וכדאי לדעת אילו.
מפתחות כפולים. JSON מרשה לאובייקט למנות את אותו מפתח פעמיים ורוב המנתחים שומרים את האחרון בשקט. YAML אוסר כפילויות מכול וכול, ולכן הפלט יהיה תקין, אבל הערך המוקדם כבר איננו — ושום ממיר אינו יכול לדווח על מה שמעולם לא קיבל.
דיוק שלמים. מספר JSON גדול מכתשעה קוודריליון אינו שורד ניתוח לתוך double, ולכן מזהה כמו 12345678901234567890 חוזר מעוגל. זו אינה בעיה של YAML ואינה בעיה שהכלי הזה מכניס; היא קורית בכל מנתח JSON בשפה. אם מזהה גדול חשוב, מקומו במחרוזת בשני הצדדים.
הערות על הפלט
ההזחה היא רווחים, תמיד, מפני ש־YAML אוסר טאבים להזחה לחלוטין — זה אחד הדברים המעטים שהפורמט קפדן לגביהם. שני רווחים הם המוסכמה; ארבעה מוצעים מפני שיש סגנונות בית שרוצים אותם.
רצפים מוזחים מתחת למפתח שלהם. גם זה וגם הצורה הלא מוזחת הם YAML חוקי ומשמעם זהה; המוזחת היא מה שרוב האנשים כותבים ומה שרוב העורכים מקפלים נכון.
מערך או אובייקט ריקים נכתבים בסגנון זרימה כזוג סוגריים, מפני שלסגנון הבלוק אין דרך לבטא ריקנות — אין מה לכתוב בשורות שאחריו.
הפלט מסתיים בשורה חדשה, וזה נושא משקל ולא סדר. חיתוך של בלוק סקלרי נמדד מול שבירת השורה שאחריו, ולכן בלוק חתוך ממש בסוף קובץ בלי שורה חדשה סופית מאבד את השורה החדשה שהיה אמור לשמור.
מניפסטים של Kubernetes, והשדות שחייבים להישאר מחרוזות
Kubernetes קורא מניפסט בשני הפורמטים. התיעוד שלו עצמו מכנה את YAML המוסכמה ומציין את JSON כחלופה, ו־kubectl ממיר מניפסט ל־JSON, או לצורת סידור אחרת שה־API תומך בה, כשהוא שולח את הבקשה — כך שההמרה אינה נוגעת למה שהאשכול יסכים לקבל. היא נוגעת לקובץ שנשאר אצלכם: YAML הוא מה שסוקר קורא, מה שדיף בבקשת מיזוג קריא בו, והיחיד מבין השניים שיכול לשאת הערה. ה־JSON בדרך כלל מגיע מ־kubectl get -o json, מתבנית, או מ־API שמחזיר אובייקטים.
- קובץ אחד, כמה אובייקטים. אפשר לקבץ מניפסטים לקובץ אחד המופרד בשלושה מקפים, והתיעוד אומר במפורש שהם נוצרים בסדר שבו הם מופיעים — ולכן 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 צריך להינתן תמיד כמחרוזת מצוטטת, כדי להימנע מהתנגשות עם הנקודה הצפה בבסיס 60 של YAML. בלי מרכאות, 22:22 הוא המספר השלם 1342.
- ערכי סביבה. התיעוד מבקש שהמילים true, false, yes ו־no יוקפו במרכאות כדי שהמנתח לא יהפוך אותן. שתיים מהארבע הן בוליאנים בשתי גרסאות YAML ושתיים — yes ו־no — ב־1.1 בלבד, וזו הסיבה שלוח הממצאים מסמן אותן אחרת.
- כלל בסיס 60 מגיע רק למיפוי שהמספר שאחרי הנקודתיים בו קטן משישים, כי זה הטווח שספרה אחת בבסיס 60 מכסה. לכן 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 זנח את בסיס 60 ומשאיר כבוליאנים רק את 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 כבר אינם בוליאנים, בסיס 60 נעלם, ואין טיפוס חותמת זמן. PyYAML מממש 1.1 ועדיין נמצא בכל מקום, ולכן הפלט השמרני הוא ברירת המחדל; הגדרת 1.2 קיימת למקרה שאתם יודעים מה יקרא את הקובץ.
- האם הוא יכול להמיר YAML חזרה ל־JSON?
- לא, במכוון. קורא YAML זקוק לעוגנים, כינויים, תגים, מפתחות מיזוג, חמישה סגנונות סקלר ושתי גרסאות סכמה, וטעות דקה באחד מהם פירושה לקבל קובץ ולהחזיר נתונים שונים ממה שהכיל. הכשל הזה שקט, וזה מה שעושה אותו גרוע מלא להציע את התכונה.
- איך אני מקבל קובץ רב-מסמכים בסגנון Kubernetes?
- הדליקו את המתג שפולט כל איבר של מערך עליון כמסמך משלו, והמערך הופך למסמכים מופרדים בשלושה מקפים. אם הקלט שלכם הוא NDJSON במקום — אובייקט JSON אחד בכל שורה, כפי שיומנים ויצואי API לעיתים קרובות — זה מזוהה אוטומטית ומדווח מעל הפלט.
- למה אין הערות בפלט?
- מפני שלא היו כאלה בקלט. הערות הן הדבר העיקרי שיש ל־YAML ואין ל־JSON, וממיר אינו יכול להמציא אותן. כדאי לזכור את זה גם בכיוון ההפוך: אם תעבירו קובץ YAML הלוך ושוב דרך JSON, כל הערה שבו נעלמה.
- האם משהו שאני מדביק נשלח לשרת?
- לא. הניתוח וההמרה רצים כולם בדפדפן שלכם; שום דבר אינו מועלה או נרשם, וזה עובד בלי חיבור רשת.
- איך ממירים פלט JSON של kubectl למניפסט YAML?
- מדביקים אותו וקוראים את ה־YAML. Kubernetes מקבל את שני הפורמטים — התיעוד שלו מכנה את YAML המוסכמה ואת JSON החלופה — ולכן הסיבה להמיר היא הקובץ שנשאר אצלכם ולא מה שהאשכול יסכים לקבל. מה שכן צריך לבדוק זה המרכאות: מניפסט מלא בשדות שה־API מגדיר כמחרוזות, ובהם ערכי סביבה וערכים של תוויות והערות, והם בדיוק אלה שהטיפוס שלהם היה משתנה. אם ה־JSON הוא רשימה של כמה אובייקטים, המתג לריבוי מסמכים הופך אותה לקובץ אחד.
- למה Docker Compose רוצה את הפורטים שלי במרכאות?
- כי 22:22 אינו זוג מספרים בעיני מנתח YAML 1.1 אלא מספר אחד בבסיס 60 — 1342. התיעוד של Compose מבית Docker אומר שמיפוי HOST:CONTAINER צריך להיות תמיד מחרוזת מצוטטת מהסיבה הזאת בדיוק, ואותו עמוד מבקש גם ש־true, false, yes ו־no בבלוק סביבה יצוטטו. שני הדברים הם מה שהכלי הזה עושה תחת סכמת ברירת המחדל, ולוח הממצאים אומר מאיזה כלל כל מרכאות באו. מיפוי שהמספר שאחרי הנקודתיים בו הוא שישים או יותר, כמו 8080:80, נמצא מחוץ לכלל בסיס 60 ונשאר פשוט.
- איזו גרסת YAML לבחור עבור Kubernetes או Docker Compose?
- את ברירת המחדל, 1.1. כל מרכאות שהיא מוסיפה הן מרכאות שגם מנתח 1.2 מקבל, ולכן הפלט השמרני בטוח בשני המקרים, ושתי האזהרות בתיעוד של Compose עוסקות בכללים של 1.1 — מיפויי פורטים בבסיס 60, ו־yes ו־no כבוליאנים. הגדרת 1.2 קיימת כדי להראות אילו מרכאות קיימות רק בשביל הסכמה הישנה; הפלט מאבד אותן, וזה ההפך ממה שהאזהרות האלה מבקשות.
כלים קשורים
- JSON ל־TypeScript
הסקת ממשקי TypeScript מדוגמת JSON.
- בודק JSONPath
בדיקת שאילתות JSONPath לפי RFC 9535 מול JSON.
- מחולל טבלאות Markdown
בניית טבלאות Markdown מתוך CSV, TSV או JSON ויישור עמודות.
- מיזוג PDF
מאחדים כמה קובצי PDF לקובץ אחד, בסדר שבחרתם — בלי להעלות דבר.