YAML vs JSON vs TOML:各自的優勢、使用場景與 YAML 的隱式型別陷阱

各格式的主場領域

每種資料序列化格式都在自己的領域稱霸。JSON(JavaScript 物件表示法,標準化為 ECMA-404 與 RFC 8259)是 HTTP API 的通用語言:幾乎每個 REST 端點都使用它,每種程式語言都有零依賴的 JSON 解析器,其語法只需一頁即可描述完整。YAML(YAML Ain't Markup Language)主導 DevOps 工具的設定檔——Kubernetes 清單、GitHub Actions 工作流程、Ansible 劇本和 Docker Compose 檔案全都採用 YAML。其多行字串和注釋支援使它遠比 JSON 更適合人類日常編輯的設定檔。TOML(Tom's Obvious, Minimal Language)是專案層級設定的首選:Rust 套件的 Cargo.toml、Python 建置中繼資料的 pyproject.toml 和 Hugo 網站設定均使用 TOML。其強型別字面值和 [section] 語法讓設定檔易於閱讀,且不會有靜默的打字錯誤。

這些領域的劃分並非偶然——它們反映了各格式的設計優先順序。JSON 重視互通性和確定性;YAML 重視人類可讀性和表達力;TOML 重視型別安全和可預測的解析。理解這些優先順序,有助於選擇正確的工具,避免將一種格式的假設帶入另一種格式的問題空間。

YAML 的隱式型別轉換:挪威問題

YAML 最惡名昭彰的陷阱是其積極的隱式型別強制轉換。在 YAML 1.1(大多數解析器直到近期仍使用的版本,包括 PyYAML < 6.0 和 Ruby 的 Psych 4.0 之前)中,未加引號的純量會被一系列正規表示式測試以確定其型別。布林測試會匹配 yesnoonofftruefalse——且不區分大小寫。這在使用 ISO 3166-1 alpha-2 國家代碼的軟體中造成了實際的資料完整性錯誤:挪威的國家代碼是 NO,YAML 1.1 會靜默地將其轉換為布林值 false。同樣的問題也會影響 fi(芬蘭,在某些解析器中解析為 false),以及任何值恰好是未加引號的 yesno 的設定鍵。像 norway: NO 這樣的映射在執行時會變成 {norway: false}——沒有錯誤,沒有警告。

YAML 1.2(2009 年發布,由 ruamel.yaml 和較新的解析器採用)將布林集合收緊為只有 truefalse(區分大小寫),消除了 yes/no/on/off 變體。然而,數百萬個生產環境檔案和讀取它們的工具仍在 YAML 1.1 解析器上執行。安全規則:始終為可能被誤讀的字串值加引號——國家代碼、旗標型文字、看起來像數字的字串。使用 'NO' 而非 NO,使用 '1e2' 而非 1e2(後者會成為浮點數 100.0),使用 '2024-01-01' 而非 2024-01-01(某些解析器會將其轉換為日期物件)。YAML 規範本身也承認這種歧義是各實作之間已知的錯誤來源。

其他 YAML 陷阱:制表符、錨點與文件分隔符

縮排是 YAML 的語法——而制表符在 YAML 縮排中從來無效。規範明確禁止在縮排位置使用制表符。然而制表符在大多數編輯器中預設是不可見的,而一個意外的制表符會產生指向錯誤行號的掃描器錯誤。解決方法是在編輯器中啟用「顯示空白字元」,並設定在 .yaml 檔案中將制表符展開為空格。相關的陷阱是混合區塊和流式樣式:key: {a: 1, b: 2} 是有效的(流式映射作為值),但縮排流式風格的內容很微妙——流式序列中的換行符會重置列計數器,視覺上看起來正確的內容可能解析方式不同。

YAML 錨點&)和別名*)允許在文件中重複使用一個節點,這對於包含重複容器規格的長 Kubernetes 清單非常有用。但它們也能讓未限制別名深度的解析器受到「十億笑聲」式的擴展攻擊:一個有 9 層別名、每層擴展 10 個引用的文件會產生超過十億個節點。大多數生產環境解析器(PyYAML 5.1+、使用 SafeLoader 的 snakeyaml)會限制別名深度;始終使用安全載入 API。--- 文件分隔符允許在一個檔案中包含多個 YAML 文件——kubectl apply -f 依賴於此——但某些解析器在未明確迭代的情況下只返回第一個文件,靜默丟棄其餘部分。

TOML 的顯式型別與日期時間優勢

TOML 在語法層面為每個值分配型別,不進行隱式強制轉換。整數無需引號(port = 8080),浮點數需要小數點(timeout = 1.5),布林值為小寫的 truefalse,字串始終需要引號。這意味著產生未加引號單詞的打字錯誤是解析錯誤,而非靜默的型別變更——解析器會拒絕載入檔案,而非繼續處理錯誤資料。TOML 1.0(2021 年發布)也規定了四種作為第一類字面值的日期時間型別:offset_date_time1979-05-27T07:32:00Z)、local_date_timelocal_datelocal_time。沒有其他主要序列化格式將日期視為內建型別——JSON 沒有;YAML 1.1 有日期型別,但其解析在各實作間不一致。

[table][[array of tables]] 語法清晰地映射到巢狀物件和物件陣列,這是 TOML 對 YAML 縮排區塊結構的回答。一個 [[servers]] 區塊後接另一個 [[servers]] 區塊會向 servers 陣列追加第二個元素——明確、可讀,且不會因一個多餘的空格而破壞。其權衡是:TOML 不支援錨點或多文件檔案,因此具有重複子樹的深層設定層次結構比其 YAML 等效形式更冗長。對於一次編寫、頻繁讀取的專案中繼資料檔案,這種冗長通常不是問題。

JSON 的嚴格性作為特性

JSON 的語法沒有可選功能、沒有型別強制轉換、沒有文件層級指令。在任何平台上,符合規範的解析器讀取 {"active": true} 都會產生一個布林值——不是字串 "true",不是整數 1。這種確定性使 JSON 成為 API 的預設傳輸格式,儘管它不如 YAML 對人類友好。RFC 8259(取代了 RFC 7159 和 RFC 4627)進一步收緊了規範:JSON 文本是一個序列化值——單個物件、陣列、字串、數字、布林值或 null。物件中的重複鍵被明確標記為「不應」(實作可能接受它們,但結果未定義),編碼必須是無 BOM 的 UTF-8。

缺少注釋是 JSON 最常被提及的抱怨。存在解決方法:JSON5(帶有注釋和尾部逗號的超集)、JSONC(VS Code 的 settings.json 使用)以及在解析前剝離 // 行。對於人類和機器都需要讀取的設定檔,這些注釋擴展有所幫助——但它們不是標準 JSON,許多解析器會拒絕它們。慣用的解決方案是使用 TOML 或 YAML 進行編寫,僅將 JSON 用作機器交換格式,由建置步驟生成。這正是 package.json 工具生態系統使用的模式:人類通過 .eslintrc.yaml 或帶有 JSONC 的 tsconfig.json 進行設定,工具輸出 JSON。

選擇正確的格式

實際的決策規則很簡單:JSON 用於任何跨越服務邊界的資料(HTTP API、訊息佇列、由程式碼生成的設定);YAML 用於操作人員手動編輯且需要注釋或多行字串的設定檔——尤其是在強制使用它的生態系統中(Kubernetes、GitHub Actions);TOML 用於強型別和扁平檔案結構比 YAML 表達力更重要的專案層級設定。如有疑問,優先使用您的生態系統已經在使用的格式——對抗慣例的代價遠超其價值。

如果您繼承了一個大型 YAML 設定檔並懷疑存在隱式型別錯誤,快速審查方法是通過 YAML 1.2 程式碼檢查器(yamllintspectral)執行它,並搜索符合布林值或數字模式的未加引號值。對於新專案,考慮添加 Schema(JSON Schema 通過工具適用於三種格式)——無論您選擇哪種格式,它都能在型別錯誤到達生產環境之前捕獲它們。TeaFun 的 YAML / JSON / TOML 轉換器讓您在瀏覽器中於三者之間切換,無需安裝即可發現結構差異。