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期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?