字串/數值/布林/null/時間戳與型別標籤
YAML 解析器會依字面自動推斷型別:看到像數字的當數字,像布林的當布林,像日期的當日期物件。這很方便,也是陷阱的來源——因為你以為是字串的值,可能被轉成別的型別。
integer: 42 float: 3.14 exp: 6.02e23 hex: 0x1F octal: 0o17 bool: true none: null date: 2026-08-15 datetime: 2026-08-15T10:30:00Z
註:hex / octal 在各解析器的支援程度不同,別依賴它——要當字串就加引號。
這是 YAML 最著名的坑。在 YAML 1.1(很多工具仍用),yes、no、on、off 都會被當成布林。YAML 1.2 只認 true / false。於是同一份檔案在不同解析器下結果可能不同。
on: 開 # 鍵 on → true! value: yes # → true(1.1)
{true: "開", # 鍵被轉成布林
"value": true}
true / false。若你想要字串「yes」,務必加引號 "yes"。鍵名也別用 on / off——除非你確定解析器支援(GitHub Actions 的 on: 是特例,見單元 7)。v1: 010 # 舊規格視為八進位 8;新規格已禁止 v2: "010" # 加引號 → 字串 "010",最安全 v3: 1e3 # → 1000.0(浮點) v4: "1e3" # 加引號 → 字串
電話號碼、郵遞區號、產品代碼這類「看起來像數字但其實是識別碼」的值,一律加引號,否則前導零會被吃掉或型別錯誤。
ISO 8601 格式會被推斷成日期/時間物件;加引號則維持字串。
created: 2026-08-15 # 日期物件 started: 2026-08-15T10:30:00Z # 帶時區 as_str: "2026-08-15" # 字串
實務注意:10:00(時分)在 YAML 1.1 會被當 sexagesimal(六十進位)數字!想當字串就加引號。
!! 可顯式指定型別,稱型別標籤(type tag)。最常用的是 !!str;!!binary 表示 base64。
icon: !!binary U29tZQ== # base64 count: !!str 42 # 強制字串 "42"
version: "1.0" # 字串,不是 1.0 replicas: 3 enabled: true phone: "0912-345-678" # 保留前導零 mode: "yes" # 字串 "yes",不是布林 start: 2026-08-15
① "1.0" 加引號保字串
② 3 整數
③ true 布林
④ 電話號碼加引號保前導零
⑤ "yes" 加引號防布林化
⑥ 日期物件
練習:寫 10 個值——5 個故意踩陷阱(yes、010、on、1e3、10:00),5 個用安全寫法修正,然後用 yaml.safe_load 印出每個值的 type() 檢查。
以下是一份看似無害但包含多個型別陷阱的 GitHub Actions 設定:
env: APP_VERSION: "1.0" # ✅ 字串 APP_VERSION_BAD: 1.0 # ❌ 變成浮點 1.0 PORT: "08080" # ✅ 保留前導零 PORT_BAD: 08080 # ❌ 1.1 變八進位,1.2 報錯 DEBUG: "off" # ✅ 字串 DEBUG_BAD: off # ❌ 1.1 變 false
APP_VERSION: "1.0" → "1.0" (字串) APP_VERSION_BAD: 1.0 → 1.0 (浮點,丟了精確度) PORT: "08080" → "08080" (字串) PORT_BAD: 08080 → 4160 (1.1 八進位) / ERROR (1.2) DEBUG: "off" → "off" (字串) DEBUG_BAD: off → false (1.1 布林)
為什麼這樣設計而非替代方案:APP_VERSION 加引號不只是「保險」——在 CI/CD 中,version 常被 shell 的 echo 直接輸出,浮點 1.0 與字串 "1.0" 在 shell 行為相同,但在程式比較(if [ "$v" == "1.0" ])或 JSON 注入時行為不同。加引號是唯一的「型別鎖」。
| 值 | YAML 1.1 | YAML 1.2 | 建議 |
|---|---|---|---|
yes / no | 布林 true / false | 字串 "yes" | 加引號 |
on / off | 布林 true / false | 字串 "on" | 加引號 |
010 | 八進位 → 8 | 字串 "010" | 加引號 |
1e3 | 浮點 1000.0 | 浮點 1000.0 | 相同 |
10:30 | sexagesimal 630 | 字串 "10:30" | 加引號 |
\n、\t 會被解讀為控制字元,而單引號不會。如果你的值含有反斜線(例如 Windows 路徑 C:\new\test),用雙引號會被解讀為 C: + 換行 + est。所以「最安全」的字串寫法是單引號(不做任何跳脫)。| 症狀 | 可能原因 | 解決方案 |
|---|---|---|
| 字串變數在 shell 中顯示為數字 | 值未加引號,被推斷為數字 | 加引號 "value" |
on 鍵在 GitHub Actions 中正常但自建工具中爆炸 | 自建工具用 YAML 1.1 解析,on 變布林 | 避免用 on / off 當鍵名 |
| 日期值變物件而非字串 | ISO 8601 格式被自動推斷為 timestamp | 加引號 "2026-08-15" |
| 前導零的數字解析結果不同 | 不同解析器版本(1.1 vs 1.2)對 0NN 處理不同 | 一律加引號或確認解析器版本 |
| 電話號碼被截斷 | 含有 e 的號碼(如 123e456)被當科學記號 | 加引號 |
yaml(1.1)和 ruamel.yaml(可選 1.2)分別解析 {yes: on, no: off, 010: 1e3},印出結果比對差異。timeout: 10:30,在 YAML 1.1 中被解析為什麼?怎麼修正?如果改成 timeout: "10:30",下游程式拿到的是字串,需要轉成秒數,怎麼做?單靠「記得加引號」無法規模化——真實專案用 JSON Schema 把型別契約寫死,讓機器強制所有 YAML 檔案遵守型別規則:
typed-config/ ├── config/ │ ├── app.yaml # 實際設定(含型別陷阱風險) │ └── schema.json # JSON Schema——YAML 的型別契約 ├── scripts/ │ └── validate.py # 驗證器(schema + 型別斷言) └── .github/workflows/ └── validate.yml # CI 強制:任何 PR 都要通過型別驗證
app: name: order-api version: "1.0" # 型別陷阱:不加引號會變浮點 port: 8080 enabled: true features: - auth - cart db: timeout: "30" # 故意當字串,schema 會拒絕 pool: 5
{
"type": "object",
"required": ["app", "db"],
"properties": {
"app": {"type": "object",
"properties": {
"version": {"type": "string"},
"port": {"type": "integer"},
"enabled": {"type": "boolean"},
"features": {"type": "array",
"items": {"type": "string"}}
}
},
"db": {"type": "object",
"properties": {
"timeout": {"type": "integer"}, # 期望整數
"pool": {"type": "integer"}
}
}
}
}
$ check-jsonschema --schemafile config/schema.json config/app.yaml
== Validation errors ==
config/app.yaml$: 'db.timeout' is not of type 'integer'
('string' was expected)
$ python3 scripts/validate.py
app.version type= <class 'str'> OK
db.timeout type= <class 'str'> FAIL (expected int)
① schema.json 是唯一型別事實來源——YAML 作者不用「記得」規則
② 陷阱值 "30"(字串)被 schema 直接抓出
③ version: "1.0" 符合 string 型別,通過
④ CI 把驗證變成 PR 門檻,型別錯誤無法合併
⑤ 修法:把 "30" 改成 30,或改 schema 允許 string
關鍵洞察:型別安全不能只靠作者自律——用 JSON Schema 把「每個欄位該是什麼型別」變成機器可執行契約,這才是大型團隊的標準做法。
yes 變 true、010 變 8——程式不報錯,但行為錯。version: 1.0 在浮點比對或字串輸出時丟失語意。yaml.load(非 safe),標籤 !!python/object/apply:os.system 可執行任意指令。型別標籤是攻擊面:永遠 safe_load,且 schema 驗證能擋下「型別怪異」的輸入。另外時間戳與數字物件在反序列化時可能觸發非預期的建構行為。JSON Schema 驗證對大型檔案有 O(n) 到 O(n·k) 的成本(k = schema 複雜度)。實務上把驗證放 CI / pre-commit(慢但全面),執行期只做最小檢查(isinstance / 型別斷言),兼顧正確性與延遲。
| 面向 | yaml | markdown | json | github | gitlab |
|---|---|---|---|---|---|
| 型別系統 | 自動推斷(有陷阱) | 無(純文字) | 明確型別(string/number/bool/null) | 語法層無型別 | pipeline 變數有型別概念 |
| 布林陷阱 | 1.1 的 yes/on 變布林 | 無此問題 | 只有 true/false | workflow 中 on 是特例 | rules 條件用布林表達式 |
| 數字處理 | 前導零 / 1e3 / 10:30 陷阱多 | 無此問題 | IEEE 754,無前導零 | issue 編號 / 數字皆字串 | pipeline 數字參數需小心 |
| 型別契約 | JSON Schema / kubeconform | 無 | JSON Schema 原生 | 無(靠平台語法檢查) | pipeline 有 typed variables |
| 日期時間 | ISO 8601 自動轉物件 | frontmatter 日期欄位 | 字串(無內建日期型別) | 無 | schedule / variables 日期處理 |
type() 驗證。safe_load 為何是安全底線,並舉例標籤注入攻擊。