與 JSON/TOML 的差別、被誰使用、設計哲學
YAML(YAML Ain't Markup Language)是一門資料序列化格式(data serialization format):用縮排與少量符號描述資料結構,程式再用解析器把它讀成 dict / object / hash。全名是反覆式縮寫(recursive acronym)——「YAML 不是標記語言」,它刻意強調自己不描述版面,只描述資料的形狀。
| 對比 | HTML | JSON | YAML |
|---|---|---|---|
| 定位 | 標記語言(版面) | 資料交換格式 | 資料序列化格式 |
| 誰好讀 | 機器/工程師 | 機器 | 人 |
| 註解 | <!-- --> | 不支援 | # 原生支援 |
| 結構方式 | 標籤 | 括號 | 縮排 |
YAML 與 JSON 描述的是同一種資料模型:對映(mapping)、序列(sequence)、純量(scalar)。兩者可以互相轉換——任何 JSON 都是一份合法的 YAML(JSON 是 YAML 1.2 的子集),反之絕大多數 YAML 也能轉成等價 JSON。
server: host: localhost port: 8080 debug: true
{"server": {"host": "localhost",
"port": 8080},
"debug": true}
| 比較 | YAML | JSON | TOML |
|---|---|---|---|
| 主要賣點 | 人最好讀 | 機器最通用 | 語法明確、不易出錯 |
| 語法風格 | 縮排 + 符號 | 大括號 / 方括號 | 鍵值對 + 表格 |
| 註解 | 支援 # | 不支援 | 支援 # |
| 多行字串 | 原生 block / folded | 需跳脫 \n | 三引號 |
| 型別推斷 | 自動(有陷阱) | 明確 | 明確 |
| 典型用途 | 設定檔、CI/CD、manifest | API 傳輸、網頁資料 | 套件設定(Cargo.toml) |
| 場景 | 代表 | 怎麼用 |
|---|---|---|
| CI/CD | GitHub Actions / GitLab CI | .github/workflows/*.yml 定義 pipeline |
| 容器 | Docker Compose | compose.yaml 描述多容器服務 |
| 叢集 | Kubernetes / Helm | Deployment、Service 等 manifest |
| 筆記與網站 | Jekyll / Hugo / Obsidian | Markdown 檔首 frontmatter 元資料 |
| 自動化 | Ansible / Airflow / Prefect | playbook、DAG 定義 |
| API 描述 | OpenAPI / Swagger | API 規格(YAML 或 JSON) |
app: name: 我的服務 env: production features: - login - billing debug: false
✅ 鍵值對冒號後都有空格
✅ 縮排一致(兩空格)
✅ 序列用 -
✅ 布林寫 true/false
✅ 存成 config.yml 並用 python3 -c "import yaml;print(yaml.safe_load(open('config.yml')))" 驗證
動手:開一個 test.yml,貼上左邊內容,用 Python / Node / 線上工具解析看看,再改成你自己的服務設定。
在 Kubernetes 生態中,Helm chart 的 values.yaml 是最常見的 YAML 設定檔。以下是一份具備多層巢狀、flow/block 混用與註解的真實場景:
# Helm chart values replicaCount: 2 image: repository: nginx tag: "1.27" # 字串,避免浮點 pullPolicy: IfNotPresent service: type: LoadBalancer port: 80 resources: limits: {cpu: "500m", memory: "128Mi"} requests: {cpu: "250m", memory: "64Mi"}
replicaCount: 2
image: {repository: "nginx",
tag: "1.27",
pullPolicy: "IfNotPresent"}
service: {type: "LoadBalancer",
port: 80}
resources:
limits: {cpu: "500m", memory: "128Mi"}
requests: {cpu: "250m", memory: "64Mi"}
為什麼這樣設計而非替代方案:tag 加引號是為了避免 1.27 被推斷為浮點數;resources 用 flow style 是因為它是簡短的 key-value 對,一行看完比四行更清晰;pullPolicy 不加引號是 plain string 的最佳實踐——值不含特殊字元,多一對引號只會增加噪音。
YAML 有兩種呈現上下文(presentation context):block context(以縮排定義結構)與 flow context(以 { } / [ ] 定義結構)。關鍵規則:flow context 中的冒號後不一定要有空格(當值是明確的標量時),但在 block context 中一定要有空格。這就是為什麼 {cpu:"500m"} 在 flow 對映中合法,但 cpu:"500m" 在 block context 中會出錯。
| 症狀 | 可能原因 | 解決方案 |
|---|---|---|
| 解析結果 key 消失 | 冒號後沒空格,被當成純字串 | 所有 key: value 確認冒號後有空格 |
| 值被轉成數字/布林 | 未加引號,被自動推斷型別 | 用 "value" 強制字串 |
| 編輯器看不出錯誤但解析爆炸 | Tab 與空格混用 | 設定編輯器「Tab → 2 spaces」 |
| 結構整層錯位 | 序列裡的對映對齊錯誤 | 用 - key 對齊,後續鍵與 key 同縮排 |
| frontmatter 讀不到 | 檔首缺少 --- 或被 BOM 污染 | 確認檔首是純 ---,無 UTF-8 BOM |
!!str 標籤與雙引號、(c) 一行包含 # 但不是註解。用 yaml.safe_load 印出型別驗證。把前 1–7 單元的概念放進一個完整專案:一個小型電商 API,所有設定都用 YAML 描述——應用設定、容器編排、CI pipeline 三層貫通。這就是 YAML 在真實世界的角色:整個專案的「膠水」。
config-first-app/ ├── app/ │ ├── config.yml # 應用設定(本單元核心) │ └── main.py # 讀取 config.yml 的服務入口 ├── deploy/ │ └── compose.yml # Docker Compose 編排 └── .github/ └── workflows/ └── ci.yml # GitHub Actions CI
app: name: order-api env: ${APP_ENV:-dev} # 環境變數注入,預設 dev port: 8080 features: - auth - cart - checkout rate_limit: window: 60 max_requests: 100
services: api: build: ../app ports: ["8080:8080"] environment: APP_ENV: production redis: image: redis:7-alpine
name: CI on: [push] jobs: validate: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: pip install pyyaml - run: python -c "import yaml;c=yaml.safe_load(open('app/config.yml'));assert c['app']['port']==8080"
$ python3 -c "import yaml;c=yaml.safe_load(open('app/config.yml'));print(c['app']['port'], c['features'])"
8080 ['auth', 'cart', 'checkout']
$ docker compose -f deploy/compose.yml config --quiet && echo "compose OK"
compose OK
$ actionlint .github/workflows/ci.yml && echo "workflow OK"
workflow OK
端到端流程:開發改 app/config.yml → push 觸發 CI → CI 先解析驗證 YAML 再跑測試 → 綠燈後用 compose 起整疊服務。任何一步的 YAML 壞掉,都會在 CI 階段被攔下,而不是等到上線才爆。
YAML 是直譯器友善、解析器昂貴的格式。對比:
| 面向 | YAML | JSON |
|---|---|---|
| 解析器實作複雜度 | 高(縮排 + flow + 標籤 + 多文件) | 低(遞迴下降即可) |
| 10 MB 設定檔解析時間 | 數百 ms(PyYAML 量級) | 數十 ms(標準 json 模組) |
| 記憶體峰值 | 更高(保留節點與行號資訊) | 較低 |
實務結論:熱路徑(每次請求都讀)用 JSON;冷路徑(開機/部署時讀一次)用 YAML。大型 values.yaml 可拆成多檔或開啟快取,減少單次解析量。
YAML 的「寬鬆」讓品質問題藏在語法正確的檔案裡:型別誤判、缺少必填欄位、拼錯鍵名。解法是三層防線:yamllint(風格)→ JSON Schema(結構)→ 解析器 + assert(行為)。
safe_load(或各語言對應的 safe 變體),並加上檔案大小/深度上限。YAML 的標籤(!!python/object)與錨點展開是任意程式執行與 DoS 的入口——單元 6、8 會深入 Billion Laughs。| 面向 | yaml | markdown | json | github | gitlab |
|---|---|---|---|---|---|
| 單元定位 | 資料序列化格式與設計哲學 | 純文字標記與文件結構 | 機器交換格式與嚴格語法 | 平台工作流與協作習慣 | CI/CD pipeline 平台 |
| 開場切入點 | 「人好讀的資料」 | 「寫作與排版一體」 | 「嚴謹的資料交換」 | 「PR/issue 驅動開發」 | 「pipeline 即程式碼」 |
| 第一單元教什麼 | 定義、JSON 關係、生態地圖 | 標題、段落、強調 | 物件、陣列、型別 | repo、branch、PR | project、pipeline、runner |
| 核心隱喻 | 縮排即結構 | 純文字即可表達結構 | 括號即結構 | 分散式版本控制 | 整合式 DevSecOps |
| 主要讀者 | 後端 / DevOps / SRE | 文件寫作者 / 部落客 | 前端 / API 工程師 | 開源維護者 / 協作者 | 企業 DevOps 團隊 |
safe_load 檢查)。