YAML vs JSON vs TOML:どの設定フォーマットをいつ使うべきか
どのプロジェクトもいずれ設定の問題に直面する。データベースの認証情報を保存し、デプロイ環境を定義し、CIパイプラインを記述し、依存関係を結びつけなければならない——そして誰かがフォーマットを選ばなければならない。JSON?YAML?TOML?その選択は噛みつかれるまで恣意的に見える。
Docker ComposeファイルはYAML。package.jsonはJSON。Rustの依存関係マニフェスト(Cargo.toml)はTOML。Pythonのパッケージング(pyproject.toml)はTOML。GitHub Actionsのワークフローはもちろんはない。これらはランダムではない——各フォーマットはそれぞれ特定の理由でそのドメインの採用競争に勝った。その理由を理解することで、「テンプレートが使っていたもの」にデフォルトするのではなく、意図的な選択ができるようになる。
この記事は意思決定ガイドだ。読み終えると、各フォーマットの本当のトレードオフを理解し、本番インシデントを引き起こしてきたYAMLの正確な罠を把握し、明確な選択フレームワークを手に入れられる。
共通の先祖:INIファイル
現代の競合者に飛び込む前に、30秒でINIファイルについて触れる価値がある——3つのフォーマットはある意味でこれに反応している。
INIファイルはこのように見える:
[database]
host = localhost
port = 5432
name = mydb
[server]
port = 8080
debug = trueシンプル、人間が読める、セクションをサポート、コメントをサポート。問題も同様にシンプルだ:標準仕様なし(各パーサーはエッジケースを異なる解釈する)、一段階を超えたネスト構造なし、配列なし、明示的な型なし。アプリケーションが別途決定しない限り、すべてが文字列だ。
INIは1997年のphp.iniには適していた。2026年のKubernetesデプロイを記述するには適していない。しかし正しい目標を特定していた——人間による編集可能性とコメント——これを現代のフォーマットは継承するか破棄するかした。
JSON:データの共通語
JSON(JavaScript Object Notation)は2000年代初頭に標準化され、APIの主要なwireフォーマットとなった。その強みは制約から来ている。
JSONが正しく行っていること
JSONにはちょうど6つの値型がある:文字列、数値、真偽値、null、配列、オブジェクト。仕様は明確だ。すべての準拠したJSONパーサーは同じドキュメントを同一に解析する。驚きはない。
{
"database": {
"host": "localhost",
"port": 5432,
"ssl": true
},
"allowed_origins": ["https://example.com", "https://api.example.com"],
"timeout_ms": null
}portの型は明確に数値だ。sslは明確に真偽値だ。allowed_originsは明確に文字列の配列だ。JSONパーサーはこれを誤読できない。
JSONはまた遍在している。すべての言語が標準ライブラリのJSONパーサーを持つ。ブラウザはネイティブに解析する。PostgreSQLのようなデータベースはネイティブのJSON列を持つ。ランタイムを共有しないシステム間のデータ交換の最小公分母フォーマットだ。
JSONが設定において失敗していること
JSONにはコメント構文がない。これはDouglas Crockford(JSONの作者)による意図的な設計決定で、データフォーマットのコメントは人々がそこに解析ディレクティブを入れることを奨励し、フォーマットの移植性を損なうという理由だ。
それはAPIレスポンスには良い原則だ。設定ファイルには深刻な問題だ。設定ファイルにはコメントが必要だ。timeout_msがなぜ10000でなく30000なのかを説明しなければならない。どのフィールドが安全に変更できるかをマークしなければならない。「ステージングではこれを100以上に設定しないこと」というノートを残さなければならない。
回避策はすべて良くない。"_comment"フィールドを使うチームもある。JSON5(コメント付きのスーパーセット)を使ってツールが処理してくれることを願うチームもある。JSONC(コメント付きJSON、VS Codeのsettings.jsonで普及した)を使うチームもある。これらはいずれも標準のJSONではない。
JSONはまた設定には冗長だ。末尾のカンマは許可されていないため、フィールドを並び替えると常に構文エラーが発生する。すべての文字列キーに二重引用符が必要だ。複数行の文字列構文がない——長いシェルコマンドは一行にエスケープしなければならない。
JSONが属するのは: APIレスポンス、サービス間のデータ交換、主にマシンが書くパッケージマニフェスト(package.json、composer.json)、そして異種システム間で解析の一貫性を保証する必要があるすべての場所。
ネットワーク経由で転送する前にこれらのJSONファイルを圧縮することでペイロードサイズを意味のある形で削減できる。また、エラーがアプリケーションに届く前にJSONをオンラインで検証することができる——一つの欠けたカンマや余分な括弧がJSONパーサーを完全に壊すため、これは思った以上に重要だ。
YAML:力と危険
YAML(YAML Ain't Markup Language)はXMLへの人間に優しい代替として設計された。表現力があり、読みやすく、DevOpsツールで広く使われている。また、一般的に使われる設定フォーマットの中で最も危険なものでもある。
YAMLが正しく行っていること
YAMLはコメント(#)を許可する。引用符なしの値から型を推論するので、"port": 5432の代わりにport: 5432と書ける。heredocに似た構文でクリーンな複数行文字列をサポートする。簡潔だ:インデントが波括弧と角括弧を置き換える。
database:
host: localhost
port: 5432
ssl: true
allowed_origins:
- https://example.com
- https://api.example.com
# これはタイムアウトが高い理由を説明するコメント
timeout_ms: 30000CI/CD設定——GitHub Actions、GitLab CI、CircleCI——では、コメント付きで入れ子になった構造をきれいに表現するYAMLの能力は本当に有用だ。GitHub Actionsのワークフローファイルは、入れ子になった条件、マトリックス戦略、環境変数ブロックを含む数百行にわたることが多い。JSONは読み不可能になるだろう。TOMLは深く入れ子になった構造には不格好だろう。
YAMLの罠
YAMLの型推論はものが間違う場所だ。パーサーは引用符なしの値を見てそれがどの型かを判断する。それが下す判断は常にあなたが期待するものとは限らない。
ノルウェー問題。 YAML 1.1(ほとんどのパーサーが実装しているもの——YAML 1.2ではない)では、裸のNOはブール値falseとして解析される。no、No、n、N、false、False、FALSE、off、Off、OFFも同様だ。ISO 3166-1の国コードリストにはノルウェーのNOが含まれる。国コードリストを持つ設定ファイルは、ノルウェーを静かにfalseに変えてしまう。
# YAML 1.1 — パーサーが実際に見るもの
country: NO # → false(真偽値)
enabled: yes # → true(真偽値)
mode: on # → true(真偽値)
debug: off # → false(真偽値)修正は文字列であるべきものすべてを引用符で囲むことだ:country: "NO"。しかしYAMLの全的魅力は引用符をつける必要がないことだ。驚きを避けるために引用符を要求することはフォーマットの読みやすさの優位性を損なう。
8進数整数。 YAML 1.1では、0で始まる数値は8進数として扱われる。ファイルパーミッションは一般的に8進数で書かれる:chmod: 0755。YAMLパーサーは0755を755ではなく十進数493として読む。これは実際のデプロイスクリプトを壊したことがある。
# YAML 1.1 — 8進数の解釈
file_mode: 0755 # → 493(8進数0755の十進数等価)複数行文字列のあいまいさ。 YAMLは複数の複数行文字列構文を提供する——リテラルブロック(|)、フォールデッドブロック(>)、インライン。これらは改行、末尾の空白、インデントに関して異なる動作をする。間違ったものを使うと静かに間違った文字列を生成する。
# リテラルブロック — 改行を保持
command: |
npm install
npm run build
# フォールデッドブロック — 改行はスペースになる(長い文章に有用)
description: >
これは長い説明で、
スペースで一行に折りたたまれます。タブ対スペース。 YAMLはインデントにタブ文字を許可しない。静かにタブを挿入するエディターは解析エラーを生成する——あるいはもっと悪く、タブストップ設定によって異なる解析をする。
YAML 1.2はこれらの問題の多く(真偽値リストを含む)を修正したが、ほとんどのツールはまだ1.1を実装している。YAMLを使うとき、あなたはツールチェーンがたまたま持っているパーサーのために書いており、それはあなたの仮定と一致するかもしれないし、しないかもしれない。
TOML:設定のために設計されたもの
TOML(Tom's Obvious Minimal Language)は、2013年にGitHubの共同創業者Tom Preston-Wernerによって、人間が書けてマシンがあいまいさなく解析できる設定ファイルフォーマットとして作られた。
TOMLが正しく行っていること
TOMLは明示的で明確な型を持つ。文字列は常に引用符で囲まれる。数値は常に数値だ。真偽値は常にtrueまたはfalse(小文字、エイリアスなし)だ。引用符なしの値からの型推論がないため、ノルウェー問題は発生しない。
[database]
host = "localhost"
port = 5432
ssl = true
allowed_origins = [
"https://example.com",
"https://api.example.com",
]
# これはタイムアウトが高い理由を説明するコメント
timeout_ms = 30000TOMLは日付と時刻に第一級のサポートを持つ(publishedAt = 2026-04-04)。JSONとYAMLはアプリケーションが自分でパースする文字列として残している。
セクションヘッダー([database]、[[servers]])は明示的で読みやすい。ドット付きキー(database.host = "localhost")でインラインのネストした値を表現できる。テーブルの配列([[servers]])でオブジェクトリストのクリーンな構文が得られる。
[[servers]]
name = "production"
host = "prod.example.com"
port = 443
[[servers]]
name = "staging"
host = "staging.example.com"
port = 443TOML仕様は正確でバージョン管理されている。すべての準拠したTOMLパーサーは同じ入力に対して同じ結果を生成する。
TOMLが不足しているところ
TOMLは浅いから中程度のネストには向いている。深くネストした構造——KubernetesマニフェストやAnsibleプレイブックに現れる種類——ではセクションヘッダー構文が繰り返しになる。YAMLのインデントベースのネストは6〜7レベルの深さに対してより自然にスケールする。
TOMLはまたJSONやYAMLよりもツールサポートが少ない。ほとんどの言語は優れたTOMLライブラリを持つが、エディターサポート(シンタックスハイライト、バリデーション、オートコンプリート)はJSONよりも一貫性が低い。
比較表
意思決定フレームワーク
マーケティング言語を取り除くと、選択は3つの質問に絞られる。
これは主にマシン生成か人間執筆か?
ファイルがコードで生成されコードで消費される場合——APIレスポンス、ビルド成果物、シリアル化されたキャッシュエントリ——JSONを使おう。最も移植性が高く、最も解析可能で、ツールサポートは普遍的だ。人間が読まないファイルにコメントは不要だ。
人間がこのファイルを定期的に書いて編集する場合——プロジェクト設定、環境の説明、ツールマニフェスト——コメントは交渉の余地がない。それが純粋なJSONを排除する。
ツールチェーンをコントロールしているか?
フォーマットを必須とするエコシステムに縛られている場合——KubernetesはYAMLを要求し、GitHub ActionsはYAMLを要求し、CargoはTOMLを要求し、package.jsonはJSON——決定することはない。エコシステムが期待するものを使おう。
ゼロから新しいツールや新しい設定ファイルを設計している場合、本当の選択肢がある。
ネストの深さはどれくらいか、またチームは微妙なバグにどれほど寛容か?
YAMLは表現力があるが脆弱だ。慎重なYAMLを書き、すべての文字列を引用符で囲み、linterを実行し、1.1対1.2の違いを理解するチームは安全に使える。速く動き、多様なエディター設定を持ち、CIでYAMLのlintingを実行しないチームは、いずれ本番でノルウェー問題か8進数バグに遭遇する。
フォーマットをコントロールしているなら、人間が書く設定にはTOMLがより安全な選択だ。明示的な型はパーサーエラーが解析時に出ることを意味し、ノルウェーがfalseになって機能フラグロジックが静かに誤動作する実行時ではない。
クイックリファレンス
- APIレスポンスまたはサービス間のデータ交換: JSON
- パッケージマニフェスト(主にマシンが書く): JSON
- Kubernetes、Docker Compose、GitHub Actions、Ansible: YAML(選択の余地なし)
- 設計している新しいプロジェクト設定ファイル: TOML
- Rustプロジェクト: TOML(
Cargo.toml) - Pythonプロジェクト: TOML(
pyproject.toml) - シンプルなキー値が必要なレガシーアプリ設定: INI(またはTOMLに移行)
設定ファイルを検証する
設定ファイルの構文エラーは診断が最も難しいバグの一つだ——アプリケーションが起動せず、エラーメッセージが謎めいていて、ミスは一つの誤った位置の文字だ。
JSONの場合、JSONをオンラインで検証してきれいに印刷するとエラーを即座に発見できる。コミット前にJSON設定ファイルをバリデーターに貼り付けるのは5秒かかり、CIの失敗の一種を防ぐ。より深いJSONワークフローのために——本番用の最小化、環境間の設定変更の差分——JSONの検証、最小化、きれいな印刷の方法に関するガイドを参照しよう。
YAMLにはyamllintが標準のCLIリンターだ。チームが本番YAMLを書くならCIで実行することは任意ではない。TOMLの場合、ほとんどの言語エコシステムには組み込みバリデーターが付属している(cargo checkは暗黙的にCargo.tomlを検証する;python -c "import tomllib; tomllib.load(open('pyproject.toml', 'rb'))"は任意のTOMLファイルを直接検証する)。
フォーマットは契約だ
どのフォーマットを選んでも、それはプロジェクトに触れるすべての開発者とツールとの契約になる。後で変更するのは辛い——不可能ではないが、辛い。PythonエコシステムでのSETUP.cfgとsetup.pyからpyproject.tomlへの移行は何年もかかり、まだ完了していない。
意図的に選ぼう。マシンが主要な消費者で移植性が重要な場合はJSON。エコシステムが必須としているか、深くネストした構造を読みやすくする必要がある場合はYAML。人間がファイルを書き、正確性が重要で、パーサーが読み込み時にあいまいさを捉えてほしい場合——6ヶ月後に実行時バグとして現れるのではなく——はTOML。
設定フォーマットは単なるファイルフォーマットではない。誰がファイルを編集し、どのツールがそれを処理し、暗黙の型強制をどれほど信頼するかについての声明だ。その声明を意図的に行おう。
今すぐJSON設定ファイルを検証したい?FileCrank JSON Viewerを試してみよう——JSONを貼り付けて、エラーを即座に発見し、読みやすくきれいに印刷する。