- 技術 💻
YAMLの書き方|構文一覧とパース結果、落とし穴10例を実測
YAMLとは、インデント(字下げ)と「キー: 値」で設定やデータを書く、人が読みやすいテキスト形式です。 GitHub Actions、Docker Compose、Kubernetes、OpenAPIなど、開発で触れる設定ファイルの多くがYAMLで書かれています。
ただし country: NO がライブラリによって "NO" にも false にもなるように、書いた文字と読まれる値がずれることがあります。この記事では、構文ごとに PyYAML・ruamel.yaml・js-yaml の3つで実際にパースしたJSON を並べ、YAML 1.1と1.2の違いによる落とし穴、yamllintでの検証までを実行結果つきでまとめます。
検証環境: Python 3.11 / PyYAML 6.0.3(YAML 1.1)/ ruamel.yaml 0.19.1(YAML 1.2)/ Node.js 22 / js-yaml 5.4.2(既定はYAML 1.2 Coreスキーマ)と js-yaml 4.3.2 / yamllint 1.38.0。
YAMLの書き方の早見表
| やりたいこと | YAMLの書き方 | パース結果(JSON) |
|---|---|---|
| キーと値 | name: 在庫アプリ |
{"name": "在庫アプリ"} |
| 数値・真偽値 | count: 10 / enabled: true |
10 / true |
| null | empty: / tilde: ~ / nul: null |
すべて null |
| 文字列として確実に扱う | zip: "0123456" |
"0123456" |
| リスト(配列) | - pen を行ごとに |
["pen", "note"] |
| マップ(入れ子) | 2スペースで字下げ | {"order": {"id": 1}} |
| 1行で書く(フロー記法) | tags: [web, api] / point: {x: 1, y: 2} |
["web","api"] / {"x":1,"y":2} |
| 改行をそのまま残す | text: | |
"line1\nline2\n" |
| 改行を空白にたたむ | text: > |
"line1 line2\n" |
| 末尾の改行を消す | |- / >- |
"line1\nline2" |
| 末尾の空行も残す | |+ |
"line1\nline2\n\n" |
| 同じ内容を使い回す | &base で定義、*base で参照 |
値がコピーされる |
| マップを合成する | <<: *base |
キーが展開される(ライブラリ設定による) |
| 1ファイルに複数データ | --- で区切る |
配列のように複数ドキュメント |
| 日付 | date: 2026-09-28 |
Pythonは date 型、js-yaml 5は文字列 |
迷ったら「数字っぽい文字列・yes/no/on/off・時刻・バージョン番号はクォートで囲む」と覚えておくと、落とし穴のほとんどを避けられます。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「YAMLの書き方|構文一覧とパース結果、落とし穴10例を実測」
YAMLの基本ルール
「コロンの後ろに半角スペース」「インデントはスペースのみ(タブ不可)」「同じ階層は位置をそろえる」「キーは重複させない」「大文字と小文字は区別」の5つが基本です。拡張子は .yaml と .yml のどちらでも動きますが、YAML公式FAQは .yaml を推奨しています。
構文ごとのパース結果一覧(3ライブラリで比較)
読み込みには次のスクリプトを使いました(コピペで動きます)。
pip install pyyaml ruamel.yaml # PyYAML 6.0.3 / ruamel.yaml 0.19.1
npm install js-yaml # 5.4.2
# py_load.py:PyYAML(1.1) と ruamel.yaml(1.2) で読み込み、JSONで表示
import sys, json, yaml
from ruamel.yaml import YAML
src = open(sys.argv[1], encoding="utf-8").read()
docs = list(yaml.safe_load_all(src))
print("PyYAML :", json.dumps(docs if len(docs) > 1 else docs[0], ensure_ascii=False, default=str))
r = YAML(typ="safe", pure=True)
docs = list(r.load_all(src))
print("ruamel :", json.dumps(docs if len(docs) > 1 else docs[0], ensure_ascii=False, default=str))
// js_load.mjs:js-yaml 5 で読み込み、JSONで表示
import { readFileSync } from 'node:fs'
import { loadAll } from 'js-yaml'
const docs = loadAll(readFileSync(process.argv[2], 'utf8'))
console.log('js-yaml :', JSON.stringify(docs.length > 1 ? docs : docs[0]))
スカラー(数値・真偽値・null)と文字列のクォート
count: 10
price: 1.5
enabled: true
empty:
tilde: ~
single: 'It''s ok'
double: "tab\tnewline\n"
single_esc: 'tab\t'
num_str: "123"
colon: "key: value"
3ライブラリ共通: {"count": 10, "price": 1.5, "enabled": true, "empty": null, "tilde": null, "single": "It's ok", "double": "tab\tnewline\n", "single_esc": "tab\\t", "num_str": "123", "colon": "key: value"}
値を書かない・~・null はすべて null です(空文字列は "")。
| 書き方 | エスケープ(\n など) |
使いどころ |
|---|---|---|
| クォートなし | 解釈しない | ふつうの文字列 |
'シングル' |
解釈しない(' は '') |
パス、正規表現 |
"ダブル" |
解釈する(\t はタブ) |
改行・タブを含める |
: や # を含む文字列、# や * & ! [ { で始まる文字列は、クォートで囲みます。
リストとマップ、ネスト
items:
- pen
- note
order:
id: 1
lines:
- sku: A-01
qty: 2
- sku: B-02
qty: 1
3ライブラリ共通: {"items": ["pen", "note"], "order": {"id": 1, "lines": [{"sku": "A-01", "qty": 2}, {"sku": "B-02", "qty": 1}]}}
「マップのリスト」は、2つ目以降のキー(qty)を1つ目(sku)と同じ位置にそろえます。
フロー記法(1行で書く)とコメント
tags: [web, api] # フロー記法のリスト
point: {x: 1, y: 2} # フロー記法のマップ
empty_list: []
url: http://example.com/#top
note: a#b
3ライブラリ共通: {"tags": ["web", "api"], "point": {"x": 1, "y": 2}, "empty_list": [], "url": "http://example.com/#top", "note": "a#b"}
YAMLはJSONをほぼそのまま書けます(JSONはYAML 1.2のサブセットとして設計)。# がコメントになるのは行頭か「空白の直後」だけなので、/#top や a#b は文字列の一部として残ります。複数行コメントの構文はなく、各行に # を付けます。
複数行の文字列(| と > と |- >- |+)
literal: |
line1
line2
folded: >
line1
line2
para2
strip: |-
line1
line2
keep: |+
line1
line2
folded_strip: >-
line1
line2
next: end
3ライブラリ共通: {"literal": "line1\nline2\n", "folded": "line1 line2\npara2\n", "strip": "line1\nline2", "keep": "line1\nline2\n\n", "folded_strip": "line1 line2", "next": "end"}
| 記号 | 行の間の改行 | 末尾の改行 | 用途の例 |
|---|---|---|---|
| |
そのまま残す | 1つ残す | スクリプト、証明書 |
> |
空白にたたむ(空行は改行) | 1つ残す | 長い説明文 |
|- |
そのまま残す | 消す | 改行不要の値 |
>- |
空白にたたむ | 消す | 1行にしたい長文 |
|+ |
そのまま残す | 空行も全部残す | 末尾の空行が必要 |
| は「見たまま」、> は「折りたたみ」、- は「末尾の改行を取る」、+ は「末尾の改行を全部残す」と覚えると選びやすくなります。
アンカー &・エイリアス *・マージキー <<
base: &base
image: app:1
restart: always
web:
<<: *base
port: 80
worker:
<<: *base
restart: "no"
list_ref: &tags [a, b]
copy: *tags
PyYAML・ruamel・js-yaml4 : "worker": {"image": "app:1", "restart": "no"}, "copy": ["a", "b"]
js-yaml 5(既定) : "worker": {"<<": {"image": "app:1", "restart": "always"}, "restart": "no"}, "copy": ["a", "b"]
アンカー(&名前)とエイリアス(*名前)はYAMLの基本機能で、全ライブラリで同じ結果になります。一方、マージキー << はYAML 1.1の追加仕様(1.2の本体仕様には含まれない)です。js-yaml 5 は既定の Core スキーマで << を展開せず、"<<" という普通のキーとして返しました(js-yaml 4.3.2は展開します)。js-yaml 5 でマージしたいときは、型定義に記載のとおり mergeTag を追加します。
import { readFileSync } from 'node:fs'
import { load, CORE_SCHEMA, mergeTag } from 'js-yaml'
const doc = load(readFileSync('07_anchor.yaml', 'utf8'), {
schema: CORE_SCHEMA.withTags(mergeTag),
})
console.log(JSON.stringify(doc.worker))
{"image":"app:1","restart":"no"}
<< の後に書いたキー(restart: "no")は、合成元より優先されます。
複数ドキュメント(---)
kind: A
---
kind: B
...
3ライブラリ共通: [{"kind": "A"}, {"kind": "B"}]
--- は開始、... は終了の記号です。読むときは safe_load_all(Python)や loadAll(js-yaml)を使います(safe_load / load ではエラー)。
日付と日時
date: 2026-09-28
datetime: 2026-09-28T10:30:00+09:00
quoted: "2026-09-28"
| ライブラリ | date |
datetime |
quoted |
|---|---|---|---|
| PyYAML 6.0.3 | datetime.date |
datetime.datetime(+09:00付き) |
str |
| ruamel.yaml 0.19.1 | datetime.date |
datetime.datetime |
str |
| js-yaml 5.4.2(既定) | string |
string |
string |
| js-yaml 4.3.2 | Date(2026-09-28T00:00:00.000Z) |
Date(UTCに変換:01:30:00.000Z) |
string |
日付は、ライブラリとバージョンで「型」が変わります。 文字列として扱いたいならクォートで囲むのが確実です。
YAMLの落とし穴10例(YAML 1.1と1.2の違いを実測)
左列の各行を1つのファイルに並べ、3ライブラリで読み込んだ結果です。PyYAMLは YAML 1.1、ruamel.yaml と js-yaml 5 は YAML 1.2 の解釈をします。
| 書いた値 | PyYAML(1.1) | ruamel.yaml(1.2) | js-yaml 5(1.2) | 対策 |
|---|---|---|---|---|
country: NO |
false |
"NO" |
"NO" |
"NO" |
answer: no |
false |
"no" |
"no" |
false か "no" |
flag_yes: yes |
true |
"yes" |
"yes" |
true と書く |
flag_on: on / off |
true / false |
文字列 | 文字列 | true / false |
y_key: y |
"y" |
"y" |
"y" |
― |
octal08: 08 |
"08"(文字列) |
8 |
8 |
"08" |
octal010: 010 |
8(8進数) |
10 |
10 |
"010" |
octal0o10: 0o10 |
"0o10"(文字列) |
8 |
8 |
― |
underscore: 1_000 |
1000 |
1000 |
"1_000"(文字列) |
1000 |
zip: 0123456 |
42798(8進数) |
123456 |
123456 |
"0123456" |
phone: 09012345678 |
"09012345678" |
9012345678 |
9012345678 |
"09012345678" |
version: 1.10 |
1.1 |
1.1 |
1.1 |
"1.10" |
python: 3.10 |
3.1 |
3.1 |
3.1 |
"3.10" |
time: 12:30 |
750(60進数) |
"12:30" |
"12:30" |
"12:30" |
time2: 1:30:00 |
5400 |
"1:30:00" |
"1:30:00" |
"1:30:00" |
hex: 0x1F |
31 |
31 |
31 |
― |
読み取れるポイントは次のとおりです。
- Norway問題:YAML 1.1 は
yes/no/on/offを真偽値とするため、国コードNOがfalseになります。YAML 1.2 の真偽値はtrue/falseだけです(PyYAML 6.0.3 でもyは文字列のままでした)。 - 先頭ゼロ:1.1 は
010を8進数、1.2 は0o10を8進数とします。郵便番号や商品コードの先頭ゼロは、どちらでもゼロが消えるか値が変わります。 電話番号はPyYAMLでは文字列(8進数に9は使えないため)、1.2では数値になり、ライブラリ移行で結果が変わる例です。 1.10は小数なので全ライブラリで1.1、12:30は1.1で60進数(12×60+30=750)になります。
js-yaml 5 でも load(src, { schema: YAML11_SCHEMA }) とすると、PyYAML と同じ1.1の解釈(country: false、zip: 42798、time: 750)になりました。
結論:ライブラリの版に左右されない書き方は「文字列にしたい値はクォート、真偽値は true/false だけ」です。
書き出し(dump)ではライブラリが自動でクォートする
プログラムからYAMLを書き出すと、ライブラリは誤解される文字列を自動でクォートします。
import yaml
d = {'name': '在庫', 'country': 'NO', 'version': '1.10', 'zip': '0123456', 'time': '12:30'}
print(yaml.safe_dump(d, allow_unicode=True, sort_keys=False))
name: 在庫
country: 'NO'
version: '1.10'
zip: '0123456'
time: '12:30'
PyYAML は allow_unicode=True がないと日本語がUnicodeエスケープになり、sort_keys=False がないとキーがアルファベット順に並び替わります。 js-yaml 5 の dump は指定なしで同じ出力でした。
よくあるエラーと直し方(実際のエラー文)
| 原因 | YAML | PyYAML 6.0.3 のエラー | js-yaml 5.4.2 のエラー |
|---|---|---|---|
| タブでインデント | server: の次行を Tab + port: 80 |
found character '\t' that cannot start any token |
tab characters must not be used in indentation (2:1) |
| インデントのずれ | id と qty が1文字ずれる |
mapping values are not allowed here |
bad indentation of a mapping entry (3:7) |
値の中の : |
msg: hello: world |
mapping values are not allowed here |
bad indentation of a mapping entry (1:11) |
| コロン後のスペースなし | port:80 の次に host: localhost |
mapping values are not allowed here(2行目) |
end of the stream or a document separator is expected (2:5) |
| キーの重複 | name: a と name: b |
エラーなし、{'name': 'b'}(後勝ち) |
duplicated mapping key (2:1) |
ruamel.yaml は重複キーで DuplicateKeyError を出しました。PyYAMLでは静かに上書きされるため、yamllintで検出するのが確実です。
mapping values are not allowed here は「インデントのずれ」か「値の中の :」をまず確認します。
なお、リストの中の - port:80 はエラーにならず、マップではなく文字列 "port:80" として読まれました(PyYAMLで確認)。
yamllintで書き方をチェックする
yamllint は、重複キー・真偽値の表記・空白の乱れまで検出するツールです。
pip install yamllint
yamllint lint.yaml
検証したファイルです(最終行は改行なし)。
country: NO
enabled: yes
name: a
name: b
list: [ a,b ]
ports:
- 8080:80
msg: trailing
no_newline: 1
yamllint 1.38.0 の出力(既定設定):
lint.yaml
1:1 warning missing document start "---" (document-start)
1:10 warning truthy value should be one of [false, true] (truthy)
2:10 warning truthy value should be one of [false, true] (truthy)
4:1 error duplication of key "name" in mapping (key-duplicates)
5:8 error too many spaces inside brackets (brackets)
5:11 error too few spaces after comma (commas)
5:12 error too many spaces inside brackets (brackets)
8:14 error trailing spaces (trailing-spaces)
9:14 error no new line character at the end of file (new-line-at-end-of-file)
NO と yes を truthy として警告し、重複キーも error になります。 終了コードは 1 なので、CIでマージ前に気づけます。
実務向けの .yamllint 設定
# .yamllint
extends: default
rules:
document-start: disable # 先頭の --- を必須にしない
truthy:
allowed-values: ["true", "false"]
check-keys: true
line-length:
max: 120
この設定で yamllint -f parsable lint.yaml を実行すると、document-start の警告が消え、lint.yaml:4:1: [error] duplication of key "name" in mapping (key-duplicates) のような1行1件の形式になりました。
GitHub Actions の on: キーも truthy の対象になるため、ワークフローには check-keys: false を使います。
GitHub Actionsのon:キーとバージョン番号
PyYAML で on:\n push:\n branches: [main] を読むと、キーが文字列ではなく True になりました。
{True: {'push': {'branches': ['main']}}}
GitHub Actions 自体は on を正しく扱いますが、自作スクリプトで読むとキーが True になります。 python-version は "3.10" とクォートします。
よくある質問
Q. 書く前に確認することは?
A. タブを使っていないか、先頭ゼロの番号・バージョン番号・時刻・yes/no/on/off・国コードをクォートしたか、重複キーがないかの3点です。最後はyamllintに任せると確実です。
Q. YAML 1.1 と 1.2 はどちらが使われていますか?
A. 仕様の最新は YAML 1.2.2(2021年)ですが、PyYAML は1.1、ruamel.yaml と js-yaml 5 は1.2 の解釈で、ツールによって異なります。クォートと true/false を使えばどちらでも同じ結果になります。
Q. インデントは2スペースと4スペースのどちらがよいですか? A. YAMLの仕様では、同じ階層でそろっていれば何スペースでも構いません。GitHub Actions、Docker Compose、Kubernetes の公式例は2スペースが多いので、迷ったら2スペースに合わせるとよいです。
Q. Pythonで安全にYAMLを読むには?
A. yaml.safe_load() を使います。 yaml.load() にUnsafeLoader等を渡すとPythonオブジェクトを生成するタグも解釈されるため、外部のYAMLには safe_load が基本です。
まとめ
- YAMLは「キー: 値」とスペースのインデントで書く。コロンの後ろのスペースとタブ禁止が基本ルール
- 複数行は
|(見たまま)、>(折りたたみ)、-(末尾改行を消す)、+(全部残す) で選ぶ - アンカー
&とエイリアス*は全ライブラリ共通、マージキー<<はライブラリ設定しだい(js-yaml 5 はmergeTagが必要) - PyYAML(1.1)では
NO→false、0123456→42798、12:30→750になり、1.2のライブラリとは結果が変わる 1.10→1.1はどのライブラリでも起こるので、バージョン番号は必ずクォート- yamllint で truthy・重複キー・空白の乱れをCIで検出すると、読み込み時のずれを事前に防げる
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「YAMLの書き方|構文一覧とパース結果、落とし穴10例を実測」
株式会社RJCでは、設定ファイル一つにも「書いた値と読まれる値が同じか」を確かめる姿勢を、プロジェクトの中で仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。