ว่าด้วยการปกป้อง YAML
(opensource.posit.co)- YAML 1.2 เป็นรูปแบบการทำซีเรียลไลซ์แบบอิงการเยื้องสำหรับไฟล์คอนฟิกแบบซ้อนที่มนุษย์เขียนเอง และควรถูกแยกออกจากคำวิจารณ์เรื่องความไม่เสถียรที่เกิดจากประสบการณ์กับ PyYAML รุ่นเก่า
- รูปแบบไฟล์คอนฟิกมีพัฒนาการมาเพื่อแก้ข้อจำกัดของยุคก่อนหน้า เช่น ความแบนของ INI, ความเยิ่นเย้อของ XML, และการไม่มีคอมเมนต์หรือสตริงหลายบรรทัดใน JSON
- TOML ชัดเจนสำหรับโครงสร้างตื้น ๆ อย่าง
pyproject.tomlและCargo.tomlแต่เมื่อเป็น โครงสร้างซ้อนลึก ภาระในการอ่านและแก้ไขจะเพิ่มขึ้นเพราะต้องเขียน path ซ้ำและใช้ array of tables - YAML 1.2 Core Schema จะตีความ
yes,no,on,off,y,nเป็นสตริง และตัดเลขฐาน 60 กับ timestamp ในฐานะชนิดหลักออกไป จึงลดปัญหา การอนุมานชนิดโดยปริยาย แบบในอดีต - py-yaml12 คือพาร์เซอร์และฟอร์แมตเตอร์ YAML 1.2 สำหรับ Python ที่พัฒนาโดยใช้ Rust ให้ค่าเริ่มต้นที่ปลอดภัย, ผ่าน
yaml-test-suite100%, และมีประสิทธิภาพแข่งขันได้กับส่วนขยาย C ของ PyYAML
ประเด็นปัญหา
- ในช่วงไม่กี่ปีที่ผ่านมา กระแสเรื่องรูปแบบไฟล์คอนฟิกได้ขยับไปทาง “YAML แย่ TOML ดี” แต่การประเมิน YAML ต้องมองทั้งประวัติ การเปลี่ยนแปลงของสเปก และสภาพของเครื่องมือในปี 2026 ไปพร้อมกัน
- คำวิจารณ์ YAML นั้นสมเหตุสมผลมาเป็นเวลานาน และแม้แต่ผู้ใช้ที่ระมัดระวังก็ยังเจอพฤติกรรมไม่คาดคิดได้ แต่สเปกได้เปลี่ยนไปแล้ว และระบบนิเวศของเครื่องมือก็กำลังตามทัน
- คำวิจารณ์ YAML ในปัจจุบันจะเข้าใจได้ก็ต่อเมื่อมองสายวิวัฒนาการของรูปแบบไฟล์คอนฟิกด้วย ซึ่งเป็นข้อถกเถียงที่รูปแบบรุ่นถัดไปคอยแก้ความสุดโต่งของรุ่นก่อนหน้าซ้ำ ๆ มาโดยตลอด
ประวัติย่อของรูปแบบไฟล์คอนฟิก
- ไฟล์ INI ปรากฏขึ้นในช่วงต้นทศวรรษ 1980 พร้อมกับ MS-DOS และ Windows ยุคแรก เป็นรูปแบบที่เรียบง่าย อ่านง่าย แก้ไขโดยมนุษย์ได้ มีคู่คีย์-ค่า, section ในวงเล็บเหลี่ยม, และคอมเมนต์ด้วยอัฒภาค
- INI เพียงพอกับความต้องการในยุคนั้น เช่น คอนฟิกไดรเวอร์อุปกรณ์, การระบุ path ของฟอนต์, และการตั้งค่าแอปพลิเคชัน แต่มีข้อจำกัดเชิงโครงสร้างคือซ้อนได้ไม่เกินหนึ่งระดับ และไม่มีสเปกทางการ ทำให้พาร์เซอร์แต่ละตัวมีภาษาถิ่นของตัวเอง
- XML ถูกนำไปใช้อย่างแพร่หลายในโลกซอฟต์แวร์องค์กรช่วงปลายทศวรรษ 1990 โดยรองรับลำดับชั้นตามอำเภอใจ, schema, namespace, การแปลงข้อมูล, และโครงสร้างแบบ self-describing แต่เมื่อใช้จริงกับไฟล์คอนฟิกกลับยาวเยิ่นเย้อมากจนดูแลด้วยมือได้ยาก
- JSON เกิดขึ้นในฐานะปฏิกิริยาแบบเบากว่า XML และด้วยความเรียบง่ายของ JavaScript object literal จึงเข้ามาแทน XML ใน Web API ช่วงปลายยุค 2000 ถึงต้นยุค 2010 แต่เมื่อใช้เป็นไฟล์คอนฟิกกลับมีข้อจำกัดเรื่องไม่มีคอมเมนต์, ไม่มีสตริงหลายบรรทัด, และไม่อนุญาต comma ต่อท้าย
- YAML เกิดในปี 2001 และ TOML ในปี 2013 เพื่อเติมช่องว่างที่ JSON ทิ้งไว้ โดย YAML ใช้ไวยากรณ์อิงการเยื้อง รองรับการซ้อนได้อิสระ, หลายเอกสาร, การอ้างอิง, และชนิดที่ผู้ใช้กำหนดเอง ส่วน TOML มุ่งเป็น “INI ที่ถูกทำให้เป็นมาตรฐาน” พร้อมชนิดแบบ explicit และสเปกทางการ
- รูปแบบใหม่ในแต่ละยุคต่างเริ่มจากการแก้ความสุดโต่งของรูปแบบก่อนหน้า และปัญหาของ YAML จำนวนมากในปัจจุบันก็เป็นผลจากสเปกบางเวอร์ชันและพาร์เซอร์ที่ผูกติดกับเวอร์ชันนั้น มากกว่าจะมาจากการออกแบบของรูปแบบเอง
ปัญหาที่เคยเป็นเหตุผลให้ต่อต้าน YAML
- คำวิจารณ์ YAML ไม่ได้เป็นปัญหาที่ถูกปั้นขึ้น แต่มีฐานมาจากประสบการณ์จริงที่โปรแกรมเมอร์พบเจอมาหลายปี
- ปัญหา Norway ที่โด่งดังที่สุดคือใน YAML 1.1 ค่า scalar แบบไม่ใส่เครื่องหมายคำพูด
NOจะถูกตีความเป็นบูลีนfalseทำให้noในรายการรหัสประเทศกลายเป็นค่าเท็จcountries: - dk - fi - is - no - se["dk", "fi", "is", false, "se"] - กฎเดียวกันนี้ยังใช้กับ
yes,on,off,y,nและรูปแบบตัวพิมพ์ใหญ่เล็กอีกหลายแบบ ขณะที่ port mapping อย่าง22:22อาจถูกอ่านเป็นจำนวนเต็มฐาน 60, หมายเลขเวอร์ชันอย่าง10.23ถูกอ่านเป็นเลขทศนิยมแทนสตริง, และค่าที่ดูเหมือนวันที่ถูกตีความเป็น timestamp - ในโค้ดด้าน data science และ machine learning ชื่อตัวแปรธรรมชาติอย่าง
nและyก็อาจถูกตีความเป็นคีย์FalseและTrueแทนที่จะเป็นคีย์สตริง ภายใต้กฎบูลีนโดยปริยายของ YAML 1.1{"variables": {"x": "features", False: "sample_size", True: "target"}} - การอนุมานชนิดโดยปริยายแบบก้าวร้าวของ YAML 1.1 ตั้งใจให้การอ่านค่าอย่าง
trueแบบไม่ใส่เครื่องหมายคำพูดดูสะดวกขึ้น แต่ในไฟล์คอนฟิกจริงกลับทำให้พฤติกรรมคาดเดาได้ยากขึ้น - ไฟล์คอนฟิกมักไม่ได้ถูกแก้ไขบ่อย และหลายครั้งก็ถูกแก้โดยคนที่ไม่ใช่ผู้เขียนเดิม การ parse ผิดแบบเงียบ ๆ จึงอาจแพร่กระจายไปทั่วทั้งระบบเป็นเวลาหลายเดือน ทำให้พื้นที่นี้เป็นจุดที่รับพฤติกรรมไม่คาดคิดได้ยากที่สุด
- ความซับซ้อนของสเปก YAML ทั้งฉบับก็เป็นภาระเช่นกัน โดยสเปก YAML 1.2.2 เป็นเอกสารขนาดใหญ่ มี 10 บท และโครงสร้าง section ที่มีเลขลำดับถึง 4 ระดับ
- รูปแบบการเขียนสตริงหลายบรรทัดมีหลายวิธี และ anchor กับ alias แม้จะสร้างระบบอ้างอิงที่ทรงพลัง แต่ก็มักหนักเกินความจำเป็นสำหรับงานคอนฟิกส่วนใหญ่
- ปัญหาความปลอดภัยจากการ deserialize object ด้วยแท็ก โดยเฉพาะช่องโหว่ของ
yaml.load()ใน Python กลายเป็นช่องทางโจมตีที่เป็นที่รู้จัก และคำวิจารณ์เหล่านี้ก็ใช้ได้จริงกับ YAML 1.1 และเครื่องมือรอบตัวมันในยุคนั้น
สิ่งที่ TOML ทำได้ดี
- TOML เป็นรูปแบบที่สะอาด อ่านง่าย และไม่กำกวม สำหรับโครงสร้างคอนฟิกที่แบนหรือซ้อนไม่ลึก
- ไวยากรณ์ TOML คุ้นตาสำหรับคนที่เคยเห็นไฟล์ INI แต่เพิ่มชนิดแบบ explicit, สเปกทางการ, และการรองรับ nested table ผ่านคีย์ที่คั่นด้วยจุด
pyproject.tomlและCargo.tomlมักมี section ที่นิยามชัดเจน ลึกเพียงหนึ่งหรือสองระดับ จึงเป็นกรณีที่ TOML เหมาะมาก- ใน TOML สตริงจะถูกครอบด้วยเครื่องหมายคำพูดเสมอ จึงไม่กำกวมว่า
noเป็นบูลีนหรือคำธรรมดา ขณะที่ integer, float, และวันที่ก็เป็นชนิดชั้นหนึ่ง และคอมเมนต์ก็ทำงานตามที่คาดหวัง - การยอมรับ PEP 518 ใน ecosystem ของ Python packaging และการใช้ Cargo ในชุมชน Rust เป็นหลักฐานว่า TOML เป็นตัวเลือกที่เหมาะกับขอบเขตปัญหาประเภทนี้
- สเปก TOML สั้นพอที่โปรแกรมเมอร์ฝีมือดีจะเขียนพาร์เซอร์ที่เข้ากันได้ในช่วงสุดสัปดาห์ได้ จึงส่งผลให้ระบบนิเวศของพาร์เซอร์มีขนาดใหญ่ ทดสอบมาดี และสม่ำเสมอ
- TOML ไม่มีปัญหาแตกแยกระหว่างเวอร์ชันแบบที่เกิดระหว่าง YAML 1.1 กับ 1.2 โดย TOML 1.0 ก็คือ TOML 1.0 เหมือนกันทุกที่
จุดที่ TOML เริ่มหนักมือ
- เมื่อไฟล์คอนฟิกต้องแสดงความลึก โครงสร้างซ้อนของ TOML จะต้องพึ่ง section header แบบคั่นด้วยจุด (
[servers.alpha]) หรือ array of tables ([[products]]) และยิ่งซ้อนมากก็ยิ่งอ่านยาก - Martin Vejnár แห่ง PyTOML เคยปฏิเสธไม่ให้ไลบรารีของตนกลายเป็น dependency ของ
pipโดยให้เหตุผลจากประสบการณ์ว่าเมื่อ schema ของคอนฟิกซับซ้อนขึ้น ไวยากรณ์ TOML จะดูไม่สวยและอ่านยาก - ใน YAML การเยื้องช่วยบอกลำดับชั้นได้ในทันที แต่ใน TOML ต้องเขียน path เต็มซ้ำใน section header ทุกครั้ง
services: web: image: nginx:latest environment: DB_HOST: postgres DB_PORT: 5432 resources: limits: memory: 512M cpu: "0.5"[services.web] image = "nginx:latest" [services.web.environment] DB_HOST = "postgres" DB_PORT = 5432 [services.web.resources.limits] memory = "512M" cpu = "0.5" - ผู้อ่าน TOML ต้องประกอบโครงสร้างแบบต้นไม้ขึ้นใหม่ในหัวจากลำดับของชื่อแบบมี qualifier ที่วางแบน ๆ และจากการวัดในเอกสารของ StrictYAML ไฟล์ TOML ใช้อักขระมากกว่าราว 50% สำหรับข้อมูลชุดเดียวกัน เพราะต้องเขียน prefix ของ path ซ้ำ
- Python ได้แสดงให้เห็นมานานแล้วว่าการใช้การเยื้องเป็นโครงสร้างนั้นไม่ใช่จุดอ่อน แต่เป็นจุดแข็ง เพราะช่วยกำจัดบั๊กประเภทที่โครงสร้างทางสายตาไม่ตรงกับโครงสร้างทางไวยากรณ์
- YAML ได้รับข้อดีของโครงสร้างแบบเยื้องนี้โดยตรง ขณะที่ TOML ไม่บังคับการเยื้อง ทำให้ความสัมพันธ์ระหว่างคีย์กับ table ที่ครอบอยู่มีอยู่แค่ใน section header ไม่ได้อยู่ในการจัดวางจริงของไฟล์
- สำหรับไฟล์คอนฟิกที่ซ้อนลึก TOML เป็นรูปแบบที่สแกนอ่านยาก และแก้ไขได้ยากโดยไม่มั่นใจ
การเปลี่ยนแปลงใน YAML 1.2
- สเปก YAML 1.2 เผยแพร่ในปี 2009 และฉบับแก้ไขเพื่อความชัดเจน 1.2.2 เสร็จสมบูรณ์ในเดือนตุลาคม 2021
- ใน YAML 1.2 Core Schema มีเพียง
true,falseและTrue,False,TRUE,FALSEเท่านั้นที่ถูกมองเป็นบูลีน ส่วนyes,no,on,off,y,nเป็นสตริงธรรมดา - literal ตัวเลขฐาน 60 ที่ทำให้เกิดปัญหา
22:22ถูกถอดออกไปทั้งหมด และ timestamp ก็ไม่ใช่ชนิดหลักอีกต่อไป ดังนั้นใน Core Schema ค่า2026-05-05ที่ไม่ใส่เครื่องหมายคำพูดจะเป็นสตริง ไม่ใช่วันที่ที่ตรวจจับอัตโนมัติ - JSON กลายเป็น subset ที่เข้มงวดและถูกต้องของ YAML 1.2 ดังนั้นเอกสาร JSON ที่ถูกต้องทุกฉบับจะ parse เป็น YAML ได้เหมือนกัน
- กฎการตีความแท็กเข้มงวดและชัดเจนขึ้น และตัวสเปกเองก็ถูกเขียนอย่างชัดเจนขึ้นพร้อมดูแลแบบเปิดบน GitHub
- YAML ที่ผู้คนวิจารณ์กันมาโดยมากคือ YAML 1.1 แต่สเปกที่ควบคุมภาษาในปัจจุบันคือ YAML 1.2 ที่ปลอดภัยและคาดเดาได้มากกว่า
- ปัญหาคือประสบการณ์ YAML ของผู้ใช้ไม่ได้มาจากสเปกโดยตรง แต่มาจากพาร์เซอร์ และสำหรับผู้ใช้ Python ส่วนใหญ่ พาร์เซอร์นั้นก็คือ PyYAML ที่ยังใช้ YAML 1.1 และแทบไม่ได้เปลี่ยน semantic หลักมาตั้งแต่ปี 2006
ภูมิทัศน์ของพาร์เซอร์ YAML ใน Python
- PyYAML เป็นไลบรารี YAML มาตรฐานโดยพฤตินัยของ Python โดยห่อ C library อย่าง LibYAML เพื่อประสิทธิภาพ และมีเส้นทางสำรองแบบ pure Python ด้วย
- PyYAML มียอดดาวน์โหลดหลายล้านครั้งต่อสัปดาห์และเป็น dependency ของแพ็กเกจจำนวนมาก แต่ยังใช้ YAML 1.1 ซึ่งเป็นรากของประสบการณ์แบบ “YAML parse รหัสประเทศเป็นบูลีน” ใน ecosystem ของ Python
- ใน repository ของ PyYAML มี open issue มากกว่า 200 รายการ และ open pull request มากกว่า 100 รายการ โครงการยังมีการดูแล แต่เคลื่อนตัวช้า และการเปลี่ยน major version ไปสู่ semantic ของ YAML 1.2 ก็ยังไม่เกิดขึ้นจริง
- ruamel.yaml รองรับ YAML 1.2 และมีความสามารถแก้ไขแบบ round-trip ที่เก็บคอมเมนต์, flow style, และลำดับคีย์ไว้ได้ จึงทรงพลังกว่า PyYAML มากสำหรับงานที่ต้องเก็บคอมเมนต์หรือแก้ไขแบบรับรู้รูปแบบเดิม
- ruamel.yaml ทำงานช้ากว่าเส้นทางเร็วแบบ C ของ PyYAML อย่างมาก เพราะโหมด round-trip เริ่มต้นใช้ implementation แบบ pure Python เป็นหลัก อีกทั้งยังมีประวัติด้าน packaging ที่ทำให้ pipeline การแจกจ่ายสับสนจากปัญหา namespace package และสาย dependency
- StrictYAML เป็นการใช้ subset ของ YAML ที่ตั้งใจตัดการอนุมานชนิดโดยปริยายทั้งหมด, แท็ก, anchor, และ flow style ออกไป
- StrictYAML ในเชิงปรัชญาใกล้กับ TOML มากกว่า YAML เต็มรูปแบบ เป็นรูปแบบที่ปลอดภัยและเรียบง่ายซึ่งใช้ไวยากรณ์การเยื้องแบบ YAML แต่รองรับเฉพาะ Python, ไม่มี implementation ในภาษาอื่น, และไม่ได้มุ่งปฏิบัติตามสเปกเต็มรูปแบบ
- ในภูมิทัศน์นี้ ยังขาดไลบรารีที่ทั้งเร็ว, รองรับ YAML 1.2 ได้ครบ, และแทนที่ interface พื้นฐานของ PyYAML ได้ง่าย
แนะนำ py-yaml12
- py-yaml12 คือพาร์เซอร์และฟอร์แมตเตอร์ YAML 1.2 สำหรับ Python ที่พัฒนาใน Rust เพื่อความเร็วและความถูกต้อง
- py-yaml12 ถูกสร้างบน
saphyrcrate ซึ่งเป็นไลบรารี YAML ของ Rust และให้ API ขนาดเล็กที่โฟกัสชัดเจน ได้แก่parse_yaml(),read_yaml()สำหรับการโหลด และformat_yaml(),write_yaml()สำหรับการทำซีเรียลไลซ์ -
ความเรียบง่าย
- ออกแบบให้กรณีใช้งานส่วนใหญ่ทำงานกับชนิด built-in พื้นฐานของ Python อย่าง
dict,list,int,float,str,Noneได้ตั้งแต่ต้นจนจบ - ในเส้นทางทั่วไปไม่มี document class พิเศษหรือ node type ที่ผู้ใช้ต้องกำหนดเอง และเพราะ YAML 1.2 เป็น superset ของ JSON ดังนั้น JSON ที่ถูกต้องทุกฉบับจึง parse ได้เหมือนกัน
- py-yaml12 ผ่านการทดสอบความเข้ากันได้
yaml-test-suiteที่ชุมชนช่วยกันดูแลสำหรับ edge case และการทดสอบความสอดคล้อง ได้ครบ 100% - ค่า
noในรายการregionsจะกลายเป็นFalseแบบเงียบ ๆ ในพฤติกรรม YAML 1.1 ของ PyYAML แต่ในพฤติกรรม YAML 1.2 ของ py-yaml12 จะคงไว้เป็นสตริง"no"ตามที่สเปกกำหนด - API สำหรับไฟล์ก็ตรงไปตรงมา โดยเมื่อเขียน Python dictionary ลงดิสก์แล้วอ่านกลับ จะได้ออบเจ็กต์เดิมกลับมาแบบ round-trip โดยไม่สูญเสียข้อมูล
- สำหรับฟีเจอร์ YAML ขั้นสูงอย่างค่าที่ติดแท็ก py-yaml12 มี wrapper type ชื่อ
Yamlให้ใช้ แต่สำหรับงานคอนฟิกทั่วไปถือเป็นทางเลือกเสริม
- ออกแบบให้กรณีใช้งานส่วนใหญ่ทำงานกับชนิด built-in พื้นฐานของ Python อย่าง
-
ความปลอดภัย
- ค่าเริ่มต้นของ py-yaml12 ไม่ได้ดีแค่ด้านความสะดวกใช้งาน แต่ยังปลอดภัยกว่า เพราะแนวทางตรงข้ามของ PyYAML คือปฏิบัติต่อแท็กเสมือนคำสั่ง จนการแค่อ่านไฟล์ YAML ก็อาจนำไปสู่การรันโค้ด Python ตามอำเภอใจได้
- หากอ่านไฟล์ YAML ที่สร้าง alias ให้กับ namespace ของแท็ก Python object-apply ของ PyYAML ด้วย
yaml.load(f, Loader=yaml.Loader)โค้ด Python จะถูก execute ก่อนที่ฟังก์ชันจะคืนค่าเป็น dictionary ธรรมดา - ในตัวอย่าง ผลลัพธ์การ parse อาจดูเป็น
{'debug': False, 'retries': 3}แต่ก่อนหน้านั้นตัวแปรสภาพแวดล้อมYAML_PAYLOAD_RANถูกตั้งเป็น'1'ไปแล้ว ทำให้ดูจากผลลัพธ์อย่างเดียวจะไม่รู้เลยว่ามีการ execute เกิดขึ้น - py-yaml12 จะไม่ execute แท็กที่ผู้ใช้ไม่ได้เลือกอย่างชัดเจน แต่จะเก็บไว้เป็นข้อมูล และแท็กที่ยังไม่ได้ประมวลผลจะถูกคืนค่าเป็น wrapper
Yamlที่เก็บทั้งค่าและแท็กไว้
-
ความเร็ว
- เบนช์มาร์กของ py-yaml12 เปรียบเทียบประสิทธิภาพการอ่านและเขียนกับเส้นทาง pure Python ปกติของ PyYAML,
CSafeLoaderและCSafeDumperที่ใช้ LibYAML, และ ruamel.yaml ครอบคลุมตั้งแต่ไฟล์ระดับกิโลไบต์ถึงเมกะไบต์ - เนื่องจาก logic หลักของการ parse และ format เขียนด้วย Rust ที่คอมไพล์แล้ว ไม่ใช่ Python แบบแปลความ py-yaml12 จึงรักษาการปฏิบัติตาม YAML 1.2 ได้ครบถ้วน พร้อมประสิทธิภาพที่แข่งขันได้กับส่วนขยาย C ของ PyYAML
- ปัจจุบันยังมีตัวเลือกในโลกไลบรารี Python ไม่มากนักที่ให้ทั้งการรองรับ YAML 1.2 แบบครบถ้วนและประสิทธิภาพระดับส่วนขยาย C ของ PyYAML พร้อมกัน
- เบนช์มาร์กของ py-yaml12 เปรียบเทียบประสิทธิภาพการอ่านและเขียนกับเส้นทาง pure Python ปกติของ PyYAML,
บทสรุป
- ข้อถกเถียง YAML กับ TOML ในแบบที่คุ้นเคยกัน มักใกล้เคียงกับการถกเถียงกับรูปแบบที่ในเวอร์ชันมีปัญหานั้นไม่ได้มีอยู่แล้ว โดยคำวิจารณ์เคยเป็นเรื่องจริง แต่มีลักษณะเชิงประวัติศาสตร์
- คำวิจารณ์ YAML มักชี้ไปที่ YAML 1.1 ที่ผู้ใช้เจอผ่าน PyYAML มากกว่าจะเป็นสเปกหรือ YAML 1.2 ที่ถูกนำไปใช้ได้ถูกต้อง
- TOML ยังเป็นตัวเลือกที่ดีสำหรับคอนฟิกที่ตื้นและแบน และ
pyproject.tomlก็เหมาะกับบทบาทนั้น แต่คำกล่าวว่า YAML ไม่ปลอดภัยหรือคาดเดาไม่ได้โดยเนื้อแท้ ย่อมใช้ไม่ได้เมื่ออยู่ต่อหน้าพาร์เซอร์ YAML 1.2 ที่เข้ากันได้ตามสเปก - คำถามสำคัญไม่ใช่ว่ารูปแบบไหนดีที่สุดในเชิงนามธรรม แต่คือรูปแบบไหนและเครื่องมือใดเหมาะกับงานเฉพาะเรื่องนั้นมากกว่า
- สำหรับไฟล์คอนฟิกแบบซับซ้อน ซ้อนลึก และเขียนโดยมนุษย์ YAML 1.2 พร้อมพาร์เซอร์สมัยใหม่เป็นคำตอบที่แข็งแกร่ง และรูปแบบก็พัฒนาด้วยการให้รุ่นถัดไปแก้จุดหยาบของรุ่นก่อนหน้า
- ใน Python สามารถลองประสบการณ์ YAML แบบสมัยใหม่และตรงตามสเปกได้ด้วย
pip install py-yaml12 - ในสภาพแวดล้อม R, r-yaml12 ก็ให้ข้อดีเดียวกัน ทั้งการรองรับ YAML 1.2 แบบครบถ้วน, ประสิทธิภาพจาก Rust, และค่าเริ่มต้นที่ปลอดภัย เช่นเดียวกับแพ็กเกจฝั่ง Python
1 ความคิดเห็น
ความคิดเห็นจาก Lobste.rs
คำอธิบายที่ว่า YAML เกิดขึ้นมาเพื่ออุดช่องโหว่ของ JSON ฟังดูแปลก เท่าที่จำได้ YAML มาจากคอมมูนิตี้ Perl และอาจจะอายุพอ ๆ กันหรือเก่ากว่า JSON ด้วยซ้ำ
พอลองไปดูประวัติจริง ก็พบว่ามี บทความน่าสนใจ จาก Ingy dot Net หนึ่งในผู้สร้าง YAML ที่ระบุจุดกำเนิดไว้ช่วงเดือนพฤศจิกายน 1999 ถึงมกราคม 2001
และเมื่อดู บทความประวัติของ JSON จะเห็นว่า JSON ในรูปแบบดั้งเดิมปรากฏขึ้นในเดือนเมษายน 2001 จากวิธีส่งข้อความจากเซิร์ฟเวอร์ไปยังไคลเอนต์ผ่าน hidden iframe และแท็ก
<script>และเพิ่งกลายเป็นฟอร์แมตอิสระจริง ๆ ตอนที่ Douglas Crockford เปิดตัว json.org ในปี 2002ดังนั้น YAML จึงเกิดก่อน JSON เล็กน้อย หรือถ้ามองแบบเผื่อ ๆ ก็ใกล้เคียงกับการพัฒนาพร้อมกันมากกว่า การบอกว่าเป็นปฏิกิริยาต่อ JSON หรือเกิดมาเพื่ออุดจุดอ่อนของ JSON จึงไม่แม่นนัก และจริง ๆ แล้ว YAML เป็น ปฏิกิริยาต่อ XML มากกว่า ส่วน JSON เองแม้จะเริ่มจากเหตุผลเชิงปฏิบัติในการใส่ข้อมูลลงในแท็ก
<script>ตรง ๆ แต่ก็คงปฏิเสธไม่ได้ว่ามันได้รับความนิยมเพราะกลายเป็นทางเลือกที่เรียบง่ายกว่า XMLตัวบทความเองก็ดูมีร่องรอยเหมือน LLM เขียน และโปรเจ็กต์ที่ยกมาก็เหมือนจะเป็นงาน vibe coding ด้วย
แต่ละประโยคฟังดูสมเหตุสมผล ทว่าพอมองรวมกันแล้วกลับชวนสับสน อีกอย่างคือมันปกป้อง TOML ว่าเป็นภาษาที่มีสเปก แต่กลับวิจารณ์ YAML ว่ามีสเปกซับซ้อน ฟังดูแปลกนิด ๆ เหมือนจะถามว่า YAML เดิมไม่มีสเปก แต่ TOML มีมาตั้งแต่แรกหรืออย่างนั้นหรือ
จริง ๆ แล้ว JSON ใกล้เคียงกับ Data-E ในสไตล์ ECMAScript มากกว่า ถ้าดู หน้าเก็บถาวรของ Data-E ก็จะเห็นว่าพวกเขาเองก็กำลังตอบสนองต่อ XML อยู่เช่นกัน
ถ้าใช้ inline table ตัวอย่าง TOML แบบ “แย่” ก็จะออกมาประมาณนี้
ก็ดูโอเคดี และถ้าวัดกันที่จำนวนตัวอักษร มันยังดูสั้นกว่าตัวอย่าง YAML ด้วยซ้ำ
แม้อาจจะเกินขอบเขตไปหน่อยเพราะเป้าหมายของบทความนี้คือการปกป้อง YAML แต่ก็น่าจะพูดถึง TOML 1.1 และฟีเจอร์ inline table ใหม่ด้วย มันแก้ 3 เรื่องที่คนไม่ชอบใน JSON ได้คือ รองรับชื่อคีย์ที่ไม่ต้องใส่เครื่องหมายคำพูด, สตริงคอมเมนต์ และ comma ท้ายรายการ
Python 3.15 รองรับ TOML 1.1 แล้ว และการยอมรับ TOML 1.1 ก็ดูจะดีกว่า YAML 1.2 มาก การอัปเดต parser ของ TOML 1.0 ไปเป็น 1.1 น่าจะเป็นงานเล็กน้อยมาก แต่การย้าย YAML 1.1 ไป 1.2 คงไม่ง่ายแบบนั้น ฉันยังหา changelog บน yaml.org แทบไม่เจอ เจอแต่สเปกก้อนใหญ่สองฉบับ
เรื่องอย่าง “Norway Problem” สำหรับฉันเป็นแค่เชิงอรรถเล็ก ๆ ของเหตุผลที่ไม่ชอบ YAML เหตุผลจริงคือมันแก้ไขยาก ใช้ whitespace ที่มีความหมาย และโดยรวมค่อนข้างซับซ้อน
ฉันคิดว่า YAML, JSON, และ TOML ต่างก็มีพื้นที่ของตัวเอง และช่องว่างที่ฉันรู้สึกว่ามีมานานนั้น https://kdl.dev/ เข้ามาเติมได้พอดี
JSON = การแลกเปลี่ยนข้อมูลที่ซ้อนกันได้ตามอำเภอใจ
YAML = ฟอร์แมตคอนเทนเนอร์ตื้น ๆ สำหรับเก็บสตริงหลายบรรทัด
TOML = ไฟล์คอนฟิกที่เกือบแบนราบ
KDL = การแทน DSL สไตล์ Ruby ในรูปข้อมูล
ฉันไม่ชอบ YAML เพราะเรื่องอย่าง Norway problem แม้ว่า YAML 1.2 จะลดปัญหาลงไปบ้าง แต่เพราะมีสตริงที่ไม่ใส่เครื่องหมายอัญประกาศ สตริงอย่าง
"","Null","true","FALSE"ก็ยังคงเป็นปัญหาอยู่ และต้องใช้อินโค้ดเดอร์ที่เข้าใจ YAML ด้วย ฉันก็ไม่ชอบความซับซ้อนโดยรวมเหมือนกัน แต่เหตุผลจริง ๆ ที่ฉัน เกลียด YAML คือเรื่องนี้PEP-8 ก็แนะนำอย่างหนักแน่นว่าไม่ควรใช้แท็บในการเยื้อง
ฉันคิดว่าตรรกะแบบ “YAML ไม่ได้แย่หรอก YAML 1.1 ต่างหากที่แย่ แต่ส่วนใหญ่ใช้พาร์เซอร์ 1.1 กัน” ใช้ได้ไม่ดีเท่าที่บทความอยากให้เป็น
ฉันชอบ YAML ที่ใช้อย่างระมัดระวัง และก็ดีใจที่มีพาร์เซอร์ประสิทธิภาพสูงสำหรับ YAML 1.2 ออกมาใหม่ แต่ถ้าเวอร์ชันที่ “แย่” ยังเป็นตัวที่คนส่วนใหญ่ใช้อยู่ ฉันก็คงเลือกใช้อย่างอื่น ถ้าฉันควบคุมไม่ได้ว่าจะใช้พาร์เซอร์ตัวไหน และเชื่อไม่ได้ว่า YAML ของฉันจะถูกตีความอย่างถูกต้อง งั้น YAML โดยรวมก็ยังคงอยู่ในสภาพ “แย่”
ทุกคนควรย้ายไป 1.2 แต่ก่อนจะถึงจุดนั้น ฉันคิดว่าการ ระวังตัวกับ YAML ก็สมเหตุสมผลดี
พอเจอประโยคที่ว่า “ข้อถกเถียง YAML vs TOML มักเป็นการโจมตีรูปแบบที่มีปัญหาในแบบที่แทบไม่มีอยู่อีกแล้ว ดังนั้นความไม่พอใจจึงเป็นเรื่องจริงแต่เป็นเรื่องทางประวัติศาสตร์” ก็ทำเอาอยากกรีดร้องใส่ GitHub Actions กับ Kubernetes
เหตุผลฝั่งปกป้องนั้นหนักแน่นดี แต่สำหรับเอกสารที่ง่ายมาก ๆ TOML อ่านง่ายกว่า และทำให้คนเลือกใช้ TOML แทน YAML ได้ง่ายกว่า
น่าเสียดายที่การเปลี่ยนการรับรู้ของนักพัฒนาในที่สาธารณะต่อเครื่องมือนั้นมีแรงเฉื่อยยาวมาก ผู้คนอ่านเรื่องเล่าบางอย่างแล้วก็ตัดสินใจ จากนั้นก็ย้ายไปใช้เครื่องมืออื่นที่ยังไม่เคยพลาดต่อหน้าสาธารณะ
ฉันอยากให้พาร์เซอร์ YAML ที่มากับ Ruby interpreter รองรับ YAML 1.2.2
แต่ฉันก็ไม่แน่ใจว่าจะสลับเวอร์ชันใหม่ให้เป็นค่าเริ่มต้นโดยไม่ทำให้ ecosystem พังได้อย่างไร
ฉันอยากให้รูปแบบไฟล์คอนฟิกสามารถระบุ schema ที่เป็นมาตรฐาน ได้ แบบนั้น editor ก็จะเห็นไฟล์คอนฟิกใด ๆ แล้วเตือนเรื่องคีย์พิมพ์ผิดหรือชนิดข้อมูลไม่ตรงได้
ควรมีการใส่เอกสารอธิบายว่าคีย์แต่ละตัวใช้ทำอะไรผ่านคำใบ้แบบ “hover” และควรให้ auto-complete คีย์ที่ใช้ได้อย่างง่ายดายด้วย ถ้ารองรับ assertion หรือ contract ง่าย ๆ เพื่อจับค่าที่ผิดได้ด้วยก็ยิ่งดี เช่น คีย์
"color"ควรต้องตรงกับ/#[a-fA-F0-9]{6}/ในอุดมคติแล้ว ควรใช้สิ่งนี้สร้างโครงสร้างข้อมูลของไฟล์คอนฟิกแบบอัตโนมัติได้ด้วย
มีรูปแบบสเปกการตรวจสอบหลายแบบ เช่น
XSDหรือRelax NGและฉันคุ้นกับ XML DTD มากที่สุด เลยพูดถึงตัวอื่นได้ไม่มาก$schemaที่คีย์ระดับบนสุดแล้วอ้างถึงไฟล์ JSON Schema ที่นิยามสคีมาที่ถูกต้อง เป็นแนวทางที่พบได้ค่อนข้างบ่อย โดยแก่นแท้แล้วมันคือ XSD สำหรับ JSON editor ที่ดีมักใช้สิ่งนี้เป็นพื้นฐานเพื่อให้ auto-complete และขีดเส้นใต้สีแดงได้ข้อดีคือ TOML กับ YAML โดยคร่าว ๆ แล้วก็เป็น JSON ที่ต่างกันแค่ไวยากรณ์ ดังนั้นจึงมักใช้ไฟล์ JSON Schema ร่วมกันได้ด้วย