YAMLを擁護する
(opensource.posit.co)- YAML 1.2は、人が直接書くネストした設定ファイルのためのインデントベースのシリアライズ形式であり、古いPyYAML体験に由来する不安定性への批判とは切り分けて考える必要がある
- 設定ファイル形式は、INIの平坦さ、XMLの冗長さ、JSONのコメント・複数行文字列の欠如といった前世代の限界を補う流れの中で変化してきた
- TOMLは
pyproject.tomlやCargo.tomlのような浅い構造では明快だが、深いネスト構造ではパスの繰り返しや配列テーブルのため、読み書き・編集の負担が増える - YAML 1.2 Core Schemaは
yes,no,on,off,y,nを文字列として扱い、60進数の数値とコア型としてのタイムスタンプを削除することで、過去の暗黙的な型推論の問題を縮小した - py-yaml12はPython向けのYAML 1.2パーサー兼フォーマッタで、Rust実装により安全なデフォルト、
yaml-test-suite100%準拠、PyYAMLのC拡張と競合できる性能を提供する
問題意識
- ここ数年で設定ファイル形式をめぐる空気は「YAMLは悪く、TOMLは良い」という方向に傾いてきたが、YAMLの評価は歴史、仕様の変化、2026年時点のツール状況をあわせて見るべき問題である
- YAML批判は長いあいだ妥当であり、慎重な利用者でさえ予想外の挙動に遭遇してきたが、仕様は変わり、ツールのエコシステムも追いつきつつある
- 現在のYAML批判は、設定ファイル形式の系譜をあわせて見ないと理解しにくく、前の形式の行き過ぎを次の形式が正すという議論が繰り返されてきた流れの一部である
設定ファイル形式の簡単な歴史
- INIファイルは1980年代初頭にMS-DOSや初期Windowsとともに登場し、キーと値の組、角括弧のセクション、セミコロンのコメントを持つ、平坦で読みやすく人が編集しやすい形式だった
- INIはデバイスドライバー設定、フォントパス指定、アプリケーション設定といった当時の要求には十分だったが、1段階以上のネストができず、正式な仕様もないため、パーサーごとに方言を実装するという構造的な限界があった
- XMLは1990年代後半にエンタープライズソフトウェアの世界で広く採用され、任意の階層、スキーマ、名前空間、変換、自己記述的な構造を提供したが、実際の設定ファイルは非常に冗長になり、手作業で保守しにくい形式だった
- JSONはXMLへの軽量な反動として登場し、JavaScriptオブジェクトリテラルの単純さを土台に2000年代後半から2010年代初頭のWeb APIでXMLを置き換えたが、設定ファイルとして使うにはコメントがなく、複数行文字列もなく、末尾カンマも禁止という制約があった
- YAMLは2001年、TOMLは2013年にJSONが残した隙間を埋めるために登場し、YAMLはインデントベースの構文で任意のネスト、複数ドキュメント、参照、ユーザー定義型を提供し、TOMLは明示的な型と正式な仕様を備えた「標準化されたINI」を志向した
- 各世代の新しい形式は前世代の過剰さを出発点としており、現在のYAMLの問題のかなりの部分は、形式設計そのものよりも特定の仕様バージョンと、それに縛られたパーサーの産物である
YAML反対の根拠だった問題点
- YAML批判は作られた問題ではなく、長年にわたり実際のプログラマーが経験してきたことに基づくものだった
- もっとも有名なNorway問題は、YAML 1.1では引用符のないスカラー
NOが真偽値falseと解釈され、国コード一覧のnoが偽値に変わってしまう挙動であるcountries: - dk - fi - is - no - se["dk", "fi", "is", false, "se"] - 同じ規則は
yes,on,off,y,nやその大小文字の各種変形にも適用され、22:22のようなポートマッピングは60進数の整数に、10.23のようなバージョン番号は文字列ではなく浮動小数点数に、日付のように見える値はタイムスタンプに解釈される問題もあった - データサイエンスや機械学習のコードで自然な変数名である
nやyも、YAML 1.1の暗黙的な真偽値規則の下では文字列キーではなくFalse、Trueキーとして解釈されうる構造だった{"variables": {"x": "features", False: "sample_size", True: "target"}} - YAML 1.1の攻撃的な暗黙的型推論は、引用符のない
trueを真偽値として読んで可読性を高める意図があったが、実際の設定ファイルでは予測不可能性を大きくする結果になった - 設定ファイルは頻繁に編集されず、元の作者ではない人が修正することも多いため、静かな誤解析が何か月もかけてシステム全体に広がりうるという点で、予想外の挙動に最も弱い領域である
- YAML仕様全体の複雑さも負担となり、YAML 1.2.2仕様は10章と4段階の番号付きセクション構造を持つかなり大きな文書である
- 複数行文字列の表現方法が多く、アンカーとエイリアスは強力な参照システムを作るが、多くの設定作業には必要以上の概念的重さがある
- タグベースのオブジェクト逆シリアライズのセキュリティ問題、特にPythonの
yaml.load()の脆弱性はよく知られた攻撃ベクトルとなり、こうした批判はYAML 1.1とその周辺ツールのエコシステムに対しては妥当だった
TOMLが得意な点
- TOMLは平坦または浅い設定構造では、整っていて読みやすく曖昧さのない形式である
- TOMLの構文はINIファイルを見たことがある人にはなじみやすく、それでいて明示的な型、正式な仕様、ドット区切りキーによるネストテーブル対応が追加されている
pyproject.tomlやCargo.tomlは通常1〜2段階程度の深さで、よく定義されたセクションと予測可能な内容を持つため、TOMLがうまく合う事例である- TOMLでは文字列は常に引用符で囲まれるため、
noが真偽値なのか単語なのか曖昧ではなく、整数・浮動小数点数・日付が第一級の型であり、コメントも期待どおりに機能する - PythonパッケージングエコシステムでのPEP 518採用や、RustコミュニティでのCargo利用は、この問題領域においてTOMLが適切な選択である根拠になっている
- TOML仕様は、有能なプログラマーなら週末で互換パーサーを書ける程度に短く、その結果としてパーサーのエコシステムが大きく、十分にテストされ、一貫しているという利点がある
- TOMLにはYAML 1.1と1.2のあいだのバージョン分裂に相当する問題がなく、TOML 1.0はどこでもTOML 1.0であるという一貫性がある
TOMLが苦しくなる場面
- 設定ファイルが深さを表現する必要がある場合、TOMLのネスト構造はドット区切りのセクションヘッダー(
[servers.alpha])や配列テーブル([[products]])に依存し、ネストが増えるほど読みにくくなる - PyTOMLのMartin Vejnárは、自身のライブラリが
pipの依存関係になることを拒否した際、設定スキーマが複雑になるとTOML構文は見栄えが悪く読みづらいという経験を理由に挙げた - YAMLではインデントが階層をひと目で伝えるが、TOMLでは各セクションヘッダーに完全なパスを繰り返さなければならないという違いがある
services: web: image: nginx:latest environment: DB_HOST: postgres DB_PORT: 5432 resources: limits: memory: 512M cpu: "0.5"[services.web] image = "nginx:latest" [services.web.environment] DB_HOST = "postgres" DB_PORT = 5432 [services.web.resources.limits] memory = "512M" cpu = "0.5" - TOMLの読者は平坦な修飾名の連続からツリー構造を頭の中で再構成しなければならず、StrictYAML文書の計測では、同じデータを表現する際にTOMLファイルは繰り返されるパス接頭辞のため約50%多くの文字数を使う
- Pythonは、インデントを構造として使う方法が弱点ではなく強みであることをはるか以前に示しており、視覚的構造と構文構造のずれから生じる種類のバグを取り除く効果がある
- YAMLはこのインデント構造の利点を受け継いでいるが、TOMLはインデントを要求しないため、キーと包含テーブルの関係はファイルの物理的配置ではなくセクションヘッダーの中にしか存在しない
- 深くネストした設定ファイルでは、TOMLは見渡しづらく、自信を持って編集しにくい形式になる
YAML 1.2の変更点
- YAML 1.2仕様は2009年に公開され、明確化改訂版である1.2.2は2021年10月に完了した
- YAML 1.2 Core Schemaでは、
true,falseとTrue,False,TRUE,FALSEだけが真偽値として認識され、yes,no,on,off,y,nは通常の文字列である 22:22問題を生んだ60進数リテラルは完全に削除され、タイムスタンプはもはやコア型ではないため、Core Schemaでは引用符なしの2026-05-05は自動検出される日付ではなく文字列になる- JSONはYAML 1.2の厳密で正しい部分集合となり、有効なJSON文書はYAMLとしても同じようにパースされる
- タグ解釈規則はより厳格かつ明確になり、仕様自体もより明瞭に書かれ、GitHubで公開メンテナンスされている
- 人々が批判してきたYAMLはYAML 1.1であり、現在言語を支配している仕様は、より安全で予測可能なYAML 1.2である
- 問題は、ユーザーのYAML体験が仕様ではなくパーサーを通して媒介される点であり、Python利用者の大半にとってそのパーサーは、YAML 1.1を実装し、2006年以降コアの意味論を変えていないPyYAMLである
Python YAMLパーサーの地形
- PyYAMLはPythonにおける事実上の標準YAMLライブラリであり、性能のためにCライブラリLibYAMLをラップし、純粋Pythonの代替経路も提供している
- PyYAMLは毎週数百万回ダウンロードされ、無数のパッケージの依存関係になっているが、YAML 1.1を実装しており、Pythonエコシステムで「YAMLが国コードを真偽値としてパースした」という体験の根源である
- PyYAMLリポジトリには200件を超える未解決issueと100件を超える未マージpull requestがあり、プロジェクトは保守されているものの動きは遅く、YAML 1.2セマンティクスへのメジャーバージョン移行は実現していない
- ruamel.yamlはYAML 1.2対応に加え、コメント、flow style、キー順序を保持するラウンドトリップ編集機能を提供し、コメント保持や書式認識編集ではPyYAMLよりはるかに強力な選択肢である
- ruamel.yamlはデフォルトのラウンドトリップモードでは主に純粋Python実装であるため、PyYAMLのCベース高速経路よりかなり遅く、名前空間パッケージ問題や依存関係チェーンのため配布パイプラインを混乱させたパッケージング履歴もある
- StrictYAMLはYAMLの意図的な部分集合を実装し、あらゆる暗黙的型推論、タグ、アンカー、flow styleを取り除いた形式である
- StrictYAMLは思想的には完全なYAMLよりTOMLに近く、YAMLのインデント構文を使う安全で単純な形式だが、Python専用であり、他言語実装もなく、仕様準拠も目標としていない
- この状況では、高速で完全なYAML 1.2準拠を提供し、PyYAMLの基本インターフェースを置き換えやすいライブラリが不足していた
py-yaml12の紹介
- py-yaml12はPython向けのYAML 1.2パーサー兼フォーマッタであり、速度と正確性のためRustで実装されたライブラリである
- py-yaml12はRustのYAMLライブラリ
saphyrクレート上に構築され、読み込み用のparse_yaml(),read_yaml(), シリアライズ用のformat_yaml(),write_yaml()という小さく焦点の定まったAPIを提供する -
単純性
- 大半のユースケースでは、
dict,list,int,float,str,Noneのような基本的なPython組み込み型だけで最初から最後まで扱える設計 - 通常経路には特別なドキュメントクラスやユーザー定義ノード型はなく、YAML 1.2はJSONの上位集合なので、すべての有効なJSONは同じようにパースされる
- py-yaml12は、コミュニティ保守のエッジケースと適合性テスト集である
yaml-test-suiteで100%準拠を達成している regionsリストのnoは、PyYAMLのYAML 1.1挙動では静かにFalseになるが、py-yaml12のYAML 1.2挙動では仕様どおり文字列"no"のまま保たれる- ファイルAPIも直接的で、Python辞書をディスクに書いて再び読み込めば同じオブジェクトが返る、ロスレスなラウンドトリップ動作をする
- タグ付き値のような高度なYAML機能には
Yamlラッパー型を提供するが、一般的な設定作業では任意である
- 大半のユースケースでは、
-
安全性
- py-yaml12のデフォルトは使いやすさだけでなく安全性も高く、PyYAMLの逆のアプローチはタグをコマンドのように扱うため、YAMLファイルを読むだけで任意のPythonコードを実行できる危険がある
- PyYAMLのPython object-applyタグ名前空間をエイリアスにしたYAMLファイルを
yaml.load(f, Loader=yaml.Loader)で読むと、普通の辞書を返す前にPythonコードが先に実行される構造である - 例ではパース結果が
{'debug': False, 'retries': 3}に見えるが、その前に環境変数YAML_PAYLOAD_RANが'1'に設定されており、結果だけ見ても実行されたことがわからないという問題がある - py-yaml12は、明示的に選ばれていないタグを実行せずデータとして保持し、未処理タグは値とタグを含む
Yamlラッパーとして返す
-
速度
- py-yaml12のベンチマークは、キロバイトからメガバイトまでのファイルサイズで、読み書き性能をPyYAMLのデフォルト純粋Python経路、LibYAMLベースの
CSafeLoader・CSafeDumper、ruamel.yamlと比較している - コアのパース・整形ロジックをインタプリタPythonではなくコンパイル済みRustで実装しているため、py-yaml12は完全なYAML 1.2準拠を維持しつつ、PyYAMLのC拡張と競合できる性能を持つ
- 現在のPythonライブラリで、完全なYAML 1.2準拠とPyYAML C拡張級の性能を同時に提供する選択肢は多くない
- py-yaml12のベンチマークは、キロバイトからメガバイトまでのファイルサイズで、読み書き性能をPyYAMLのデフォルト純粋Python経路、LibYAMLベースの
結論
- 一般的なYAML対TOMLの論争は、問題のあった姿のままではもはや存在しない形式を相手にした議論に近く、批判は現実のものだったが歴史的な性格を持つ
- YAML批判は、仕様や正しく実装されたYAML 1.2ではなく、PyYAMLを通じて経験されたYAML 1.1を指している場合が多い
- TOMLは浅く平坦な設定には依然として良い選択であり、
pyproject.tomlもその役割に適しているが、YAMLが本質的に安全でない、あるいは予測不可能だという主張は、互換性のあるYAML 1.2パーサーの前では成り立たない - 重要なのは、抽象的にどの形式が最良かではなく、どの形式とどのツールが特定の作業に適しているかという問いである
- 複雑でネストした、人が書く設定ファイルには、最新パーサーを備えたYAML 1.2が有力な答えであり、形式は前世代の粗い部分を次世代が正す形で改善されていく
- Pythonでは
pip install py-yaml12で、現代的で仕様準拠のYAML体験を試せる - R環境ではr-yaml12が同じ利点を提供し、完全なYAML 1.2準拠、Rustベースの性能、安全なデフォルトをPythonパッケージと同様に提供する
1件のコメント
Lobste.rs の意見
YAML が JSON の隙間を埋めるために登場したという説明は妙だ。記憶では YAML は Perl コミュニティから出てきたもので、JSON と同時期か、むしろそれより古い可能性がある
実際の歴史を調べると、YAML の創始者の一人である Ingy dot Net の興味深い記事では、その起源を 1999年11月〜2001年1月の間としている
また、JSON の歴史に関する記事を見ると、原始的な JSON 形式は 2001年4月に隠し iframe と
<script>タグでサーバからクライアントへメッセージを送る方式の中で登場し、Douglas Crockford が json.org を公開した 2002年になってようやく独立した形式になったしたがって、YAML は JSON より少し先行していたか、甘めに見ても両者はほぼ同時に発展したと見るのが近い。JSON への反応として、あるいは JSON の欠点を埋めるために生まれたという説明は正確ではなく、YAML は実際には XML への反応だった。JSON も
<script>タグにデータをそのまま入れるという実用的な理由から始まったが、XML より単純な代替として定着し人気を得たのは否定しがたい記事自体にも LLM が書いたような痕跡が見え、紹介されているプロジェクトもバイブコーディングっぽい
文ごとにはもっともらしいが、全体としては混乱する。また、TOML は仕様のある言語だと擁護しながら、YAML は複雑な仕様があると批判するのも少し変だ。まるで YAML にはもともと仕様がなく、TOML にはあったかのようにも見える
JSON は実際のところ ECMAScript 風の Data-E に近い。Data-E のアーカイブページを見ると、彼らもまた XML への反応として動いていたことが分かる
インラインテーブルを使えば、「悪い」TOML の例はこうなる
見た目は悪くないし、文字数だけで比べるなら YAML の例よりさらに短く見える
この記事の目的が YAML の擁護である点を考えると範囲外かもしれないが、TOML 1.1 と新しいインラインテーブル機能にも触れてほしかった。JSON で嫌だった 3 つの点を解決してくれる。引用符なしのキー名、コメント文字列、末尾カンマをサポートしている
Python 3.15 は TOML 1.1 をサポートしており、TOML 1.1 の採用状況は YAML 1.2 よりはるかに良さそうに見える。TOML 1.0 パーサを 1.1 に更新するのはほぼ些細な作業で済む可能性が高いが、YAML 1.1 を 1.2 に上げるのはそうはいかなさそうだ。yaml.org でも変更点の一覧をうまく見つけられず、巨大な仕様書が 2 つ見えるだけだった
「Norway Problem」のような話は、自分が YAML を嫌う理由の中では小さな脚注に近く、本当に嫌なのは編集しづらく、意味のある空白を使い、全体としてかなり複雑なところだ
YAML、JSON、TOML にはそれぞれ自分の領域があると思う。長いこと空いていると感じていた場所は https://kdl.dev/ が埋めてくれた
JSON = 任意にネストできるデータ交換
YAML = 複数行文字列を入れるための浅いコンテナ形式
TOML = ほぼフラットな設定ファイル
KDL = Ruby スタイルの DSL をデータとして表現
YAMLは Norway problem のようなものがあるので嫌い。YAML 1.2で多少は軽減されたが、引用符なし文字列のせいで
"","Null","true","FALSE"のような文字列も依然として問題になり、YAMLを理解するエンコーダが必要になる。全体的な複雑さも嫌だが、私が YAML を憎む本当の理由はこれPEP-8 でもインデントにタブを使わないよう強く推奨している
「YAML が悪いのではなく YAML 1.1 が悪かったのであって、しかも大半は 1.1 パーサだ」という理屈は、この記事が期待するほどには効かないと思う
節度を持って使う YAML は好きだし、YAML 1.2 向けの高性能パーサが新しく出てくるのも歓迎だが、「悪い」バージョンが依然として多数派なら、私は別のものを使うと思う。どのパーサが使われるか制御できず、自分の YAML が正しく解釈されると信じられないなら、YAML 全体は依然として「悪い」状態だ
みんなが 1.2 に移行すべきだが、それまではYAML を慎重に扱うことは妥当だと思う
「YAML 対 TOML 論争は、たいていはもはや問題のある形では存在しない形式を攻撃する論争であり、不満は現実のものだが歴史的なものだ」という一文には、GitHub Actions と Kubernetesを前にして悲鳴を上げたくなる
擁護の理屈は強い。それでも、ごく単純な文書ではTOML の方が読みやすく、人に YAML より TOML を使ってもらう方が簡単だ
残念ながら、ツールに対する開発者の公的な認識を変えるには長い慣性がある。人は何かの話を読んで心を決め、その後は公の失敗をしていない別のツールへ移ってしまう
Ruby インタプリタに同梱されている YAML パーサがYAML 1.2.2をサポートしてくれたらと思う
ただ、生態系を壊さずに新しいバージョンへデフォルトを切り替える方法はよく分からない
設定ファイル形式が標準化されたスキーマを指定できるといい。そうすればエディタが任意の設定ファイルを見て、タイプミスしたキーや型の不一致を知らせられる
キーが何のためのものかを「hover」ヒントで文書化し、有効なキーの自動補完も簡単に提供できるべきだ。無効な値を捕まえるための簡単なアサーションや契約までサポートできればなお良い。たとえば
"color"キーは/#[a-fA-F0-9]{6}/に一致しなければならない、といった具合だ理想を言えば、これで設定ファイルのデータ構造も自動生成できるべきだ
XSDやRelax NGのような各種の検証仕様形式があり、私は XML の DTD に最もなじみがあるので、他についてはあまり語れない$schema属性を置き、正しいスキーマを定義した JSON Schema ファイルを参照する方式がかなり一般的だ。本質的には JSON 向けの XSD である。良いエディタなら通常、これを基に自動補完や赤い波線を提供できる良い点は、TOML と YAML はだいたい文法だけが違う JSON なので、通常はJSON Schemaファイルも一緒に使えることだ