單元 5 · 資料型別

字串/數值/布林/null/時間戳與型別標籤

5.1 原理:自動推斷型別

YAML 解析器會依字面自動推斷型別:看到像數字的當數字,像布林的當布林,像日期的當日期物件。這很方便,也是陷阱的來源——因為你以為是字串的值,可能被轉成別的型別

核心法則:想要「一定、永遠」是字串,就加引號。這是唯一通用作法。

5.2 常用型別一覽

原始碼與推斷結果 · TYPES
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 在各解析器的支援程度不同,別依賴它——要當字串就加引號。

5.3 布林陷阱:yes / on / off / 1

這是 YAML 最著名的坑。在 YAML 1.1(很多工具仍用),yesnoonoff 都會被當成布林。YAML 1.2 只認 true / false。於是同一份檔案在不同解析器下結果可能不同。

YAML 1.1(常見陷阱)
on:         # 鍵 on → true!
value: yes     # → true(1.1)
渲染結果 · 1.1 推斷
{true: "開",     # 鍵被轉成布林
 "value": true}
安全寫法:一律寫 true / false。若你想要字串「yes」,務必加引號 "yes"。鍵名也別用 on / off——除非你確定解析器支援(GitHub Actions 的 on: 是特例,見單元 7)。

5.4 數字陷阱:前導零與指數

數字陷阱 · NUMBER TRAPS
v1: 010          # 舊規格視為八進位 8;新規格已禁止
v2: "010"        # 加引號 → 字串 "010",最安全
v3: 1e3          # → 1000.0(浮點)
v4: "1e3"        # 加引號 → 字串

電話號碼、郵遞區號、產品代碼這類「看起來像數字但其實是識別碼」的值,一律加引號,否則前導零會被吃掉或型別錯誤。

5.5 時間戳 · Timestamps

ISO 8601 格式會被推斷成日期/時間物件;加引號則維持字串。

YAML · 原始碼
created: 2026-08-15          # 日期物件
started: 2026-08-15T10:30:00Z  # 帶時區
as_str: "2026-08-15"           # 字串

實務注意:10:00(時分)在 YAML 1.1 會被當 sexagesimal(六十進位)數字!想當字串就加引號。

5.6 強制字串與型別標籤

!! 可顯式指定型別,稱型別標籤(type tag)。最常用的是 !!str!!binary 表示 base64。

YAML · 原始碼
icon: !!binary U29tZQ==       # base64
count: !!str 42            # 強制字串 "42"
實務:標籤很少用——「加引號」更直覺、更可移植。標籤在「程式讀取時要精確型別」或「產生特殊型別」才需要。

5.7 Worked Example:一份帶型別陷阱的設定

原始碼 · YAML SOURCE
version: "1.0"            # 字串,不是 1.0
replicas: 3
enabled: true
phone: "0912-345-678"   # 保留前導零
mode: "yes"             # 字串 "yes",不是布林
start: 2026-08-15
拆解 · ANATOMY

"1.0" 加引號保字串
3 整數
true 布林
④ 電話號碼加引號保前導零
"yes" 加引號防布林化
⑥ 日期物件

練習:寫 10 個值——5 個故意踩陷阱(yes、010、on、1e3、10:00),5 個用安全寫法修正,然後用 yaml.safe_load 印出每個值的 type() 檢查。

看完這單元你應該能說出:
  • 說明 YAML 依字面自動推斷型別的機制。
  • 避開 yes/on/off 在 YAML 1.1 的布林陷阱。
  • 解釋前導零與 1e3 的數字陷阱。
  • 用引號或 !!str 強制把值當字串。

延伸閱讀


進階真實情境 Worked Example:CI/CD 中的型別陷阱連環爆

以下是一份看似無害但包含多個型別陷阱的 GitHub Actions 設定:

原始碼 · ci.yml
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
YAML 1.1 vs 1.2 結果
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 vs 1.2 的型別差異完整對照

YAML 1.1YAML 1.2建議
yes / no布林 true / false字串 "yes"加引號
on / off布林 true / false字串 "on"加引號
010八進位 → 8字串 "010"加引號
1e3浮點 1000.0浮點 1000.0相同
10:30sexagesimal 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)被當科學記號加引號

進階挑戰題

  1. 1.1 vs 1.2 比對實驗:用 Python 的 yaml(1.1)和 ruamel.yaml(可選 1.2)分別解析 {yes: on, no: off, 010: 1e3},印出結果比對差異。
  2. 型別安全清單:列出 10 個你日常會放在 YAML 裡的值(版本號、port、ID、日期、金額、布林開關等),逐一判斷哪些需要加引號、哪些可以 plain,並說明理由。
  3. 真實 bug 排查:一份設定檔中有 timeout: 10:30,在 YAML 1.1 中被解析為什麼?怎麼修正?如果改成 timeout: "10:30",下游程式拿到的是字串,需要轉成秒數,怎麼做?

① 專案級端到端 Worked Example:型別安全設定檔治理專案

單靠「記得加引號」無法規模化——真實專案用 JSON Schema 把型別契約寫死,讓機器強制所有 YAML 檔案遵守型別規則:

產出檔案樹

typed-config/
├── config/
│   ├── app.yaml            # 實際設定(含型別陷阱風險)
│   └── schema.json         # JSON Schema——YAML 的型別契約
├── scripts/
│   └── validate.py         # 驗證器(schema + 型別斷言)
└── .github/workflows/
    └── validate.yml        # CI 強制:任何 PR 都要通過型別驗證

關鍵檔案內容

config/app.yaml
app:
  name: order-api
  version: "1.0"        # 型別陷阱:不加引號會變浮點
  port: 8080
  enabled: true
  features:
    - auth
    - cart
db:
  timeout: "30"         # 故意當字串,schema 會拒絕
  pool: 5
config/schema.json
{
  "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 把「每個欄位該是什麼型別」變成機器可執行契約,這才是大型團隊的標準做法。

② 效能/品質/安全深度

品質:型別誤判的三種真實後果

安全:型別可被用來攻擊

安全提醒:不可信的 YAML 若被 yaml.load(非 safe),標籤 !!python/object/apply:os.system 可執行任意指令。型別標籤是攻擊面:永遠 safe_load,且 schema 驗證能擋下「型別怪異」的輸入。另外時間戳與數字物件在反序列化時可能觸發非預期的建構行為。

效能:型別檢查的成本

JSON Schema 驗證對大型檔案有 O(n) 到 O(n·k) 的成本(k = schema 複雜度)。實務上把驗證放 CI / pre-commit(慢但全面),執行期只做最小檢查(isinstance / 型別斷言),兼顧正確性與延遲。

③ 站際比較對照表

面向yamlmarkdownjsongithubgitlab
型別系統自動推斷(有陷阱)無(純文字)明確型別(string/number/bool/null)語法層無型別pipeline 變數有型別概念
布林陷阱1.1 的 yes/on 變布林無此問題只有 true/falseworkflow 中 on 是特例rules 條件用布林表達式
數字處理前導零 / 1e3 / 10:30 陷阱多無此問題IEEE 754,無前導零issue 編號 / 數字皆字串pipeline 數字參數需小心
型別契約JSON Schema / kubeconformJSON Schema 原生無(靠平台語法檢查)pipeline 有 typed variables
日期時間ISO 8601 自動轉物件frontmatter 日期欄位字串(無內建日期型別)schedule / variables 日期處理

④ 互動式檢核清單