すべてのプロジェクトには設定が必要で、主な選択肢は3つ:JSON、YAML、TOML。3つとも広く使ってきましたが、それぞれ違う方法でイライラさせてくれます。ここでは、いつ何を使うべきかについて正直な意見を述べます。

JSON:万能の兵士

JSONはすでにご存知でしょう。どこにでもあります。あらゆる言語でパースできます。しかし設定ファイルとしての弱点を正直に見てみましょう:

  • コメントが書けない。 設定項目の意味を文字通り説明できません。設定ファイルとしてこれはありえません。
  • 末尾のカンマが使えない。 最後に新しい行を追加して、前の行にカンマを付け忘れると — 設定が壊れます。
  • あらゆるところに引用符。 すべてのキーにダブルクォートが必要:{"port": 8080}(単にport: 8080ではなく)。

それにもかかわらず、JSONはnpm(package.json)、TypeScript(tsconfig.json)、VS Code(settings.json)などで設定に使われています。理由は?曖昧さゼロ。JSONは厳格すぎて解釈の仕方が一つしかないのです。

YAML:美しいが危険

YAMLは見た目が素晴らしい。クリーンで、ミニマルで、人間が読みやすい。しかし悪名高い落とし穴がいくつかあります:

yaml

これがYAMLの記事で触れた「ノルウェー問題」です。YAML 1.1はNOYESONOFFをブーリアンとして扱います。YAML 1.2で修正されましたが、多くのパーサーはまだデフォルトで1.1の動作を使っています。

さらにインデントの感度があります。スペース1つのずれでファイルの意味が完全に変わる可能性があり — そしてエラーメッセージはひどいものになるでしょう。

それでもYAMLはDocker ComposeKubernetes、GitHub Actions、Ansibleの標準です。DevOps/クラウドの分野で働いているなら、YAMLの習熟は必須です。

TOML:設定ファイルのスペシャリスト

TOML(Tom's Obvious Minimal Language)は設定専用に設計されました。「明白」であることを目指しています — つまり、TOMLファイルを読み間違えることは(ほぼ)不可能ということです。

TOML設定はこのような見た目です:

toml

TOMLが本当に得意なこと:ネイティブの日時型(created = 2026-03-08T10:30:00Z)、インデント問題なし、#でコメント可能、そして本当に間違えにくい構文。

弱点は?深くネストされた構造が冗長になること。TOMLはフラットな設定には最適ですが、5段階のネストが必要な場合は、YAMLやJSONの方が読みやすいかもしれません。

TOMLはRust(Cargo.toml)、Python(pyproject.toml)、Hugo、そして増え続けるツールで使われています。

私のおすすめ

シナリオ選択理由
マシン生成の設定JSON最も厳格で互換性が高い
DevOps/インフラYAMLエコシステムが求める
アプリ設定TOMLコメント + 最小限の曖昧さ
シンプルなキー・バリュー設定TOMLフラットな設定に最もクリーン
複雑なネスト構造YAML最適なネスト構文

まとめ

3つのフォーマットで同じ設定を見比べる

同じデータを3つのフォーマットで見ると、違いが本当によくわかります。シンプルなアプリ設定を例にしましょう:

JSON:

json

YAML:

yaml

TOML:

toml

JSONはすべての引用符と中括弧が必要で、YAMLは最も簡潔だがインデントに依存し、TOMLは明示的なセクションヘッダーでインデント依存なしの中間を取っていることに注目してください。

よくある設定ファイルのミス

YAML:意図しない型変換。 ノルウェー問題の他にも、YAMLは3.10を数値3.1として解釈し(末尾のゼロを削除)、1_0001000として解釈します。設定値が3.10のようなバージョン文字列の場合、必ず引用符で囲みましょう:version: "3.10"

JSON:順序が重要でないことを忘れる。 JSON仕様ではオブジェクトは順序不定です。設定の処理がキーの順序に依存している場合、脆い基盤の上に構築していることになります。挿入順序を保持するパーサーもあれば、しないものもあります。

TOML:テーブルの配列との混乱。 TOMLはテーブルの配列に[[二重.括弧]]を使いますが、これは初心者をつまずかせます。複数のサーバーを定義する方法:

toml

環境固有の設定

3つのフォーマットすべてが苦労する分野の一つが、環境固有のオーバーライド(開発 vs ステージング vs 本番)の処理です。一般的な解決策には以下があります:

  • 複数ファイル: config.base.yaml + config.production.yaml をディープマージ
  • 環境変数の補間: 一部のYAMLツールは${DB_HOST}構文をサポートしていますが、標準ではありません
  • 外部ツール: Doppler、HashiCorp Vault、またはクラウドネイティブの設定サービス

個人的には、シンプルなアプローチを好みます:妥当なデフォルト値を持つ1つの設定ファイルと、環境間で変わるもののために環境変数を使う。3つのフォーマットすべてがアプリケーションレベルで環境変数を読み取れるので、設定フォーマット自体が補間をサポートする必要はありません。

エコシステム別のフォーマット人気度

エコシステム主要フォーマット代表例
JavaScript/Node.jsJSONpackage.json, tsconfig.json, .eslintrc.json
PythonTOMLpyproject.toml, Cargo.toml (Rust)
DevOps/CloudYAMLdocker-compose.yml, k8sマニフェスト, GitHub Actions
GoTOML/YAML両方広く使われ、単一の標準なし
.NETJSONappsettings.json(XMLベースのweb.configを置き換え)

まとめ

唯一の正解はありません。最大の互換性が必要ならJSONを使いましょう。エコシステムが求めるならYAMLを使いましょう(KubernetesがTOMLを受け入れ始めることはないでしょう)。コメントと最小限のサプライズが欲しいならTOMLを使いましょう。

JSONCとJSON5:補助輪付きJSON

さて、コメントと末尾のカンマをサポートしないことでJSONを批判してきました。でも誰も教えてくれないことがあります:実はこれらの不満を解消するJSONの変種が存在し、おそらく気づかないうちに使っていたことでしょう。

まず:JSONC(JSON with Comments)。tsconfig.jsonを開いて「ちょっと待って、ここにコメントがある... JSONはコメントを許可しないんじゃ?」と思ったことがあるなら — はい、それがJSONCです。基本的にJSONですが、//の一行コメントと/* */のブロックコメントが使えます。VS CodeはJSONCを使用してsettings.jsonlaunch.jsonkeybindings.jsonファイルを管理しています。TypeScriptのtsconfig.jsonも技術的にはJSONCであり、厳密なJSONではありません。

JSONCはこのような見た目です:

jsonc

さらにJSON5があり、こちらはさらに進んでいます。JSON5はJSONの最も厳格なルールのいくつかを緩和します:

  • シングルクォートの文字列:{'name': 'Sarah'} — ついに!
  • 末尾のカンマ:{"a": 1, "b": 2,} — diffのノイズがなくなる
  • クォート不要のキー(有効な識別子の場合):{name: "Sarah"}
  • 16進数:0xFF
  • バックスラッシュ継続による複数行文字列
  • Infinity-InfinityNaNが有効な数値として使用可能

JSON5は人間が主な読者である設定ファイルに最適です。一部のツールはネイティブでサポートしています — 例えばBabelの.babelrcはJSON5にできます。しかしAPIレスポンスやデータ交換にJSON5を使わないでください。厳密なJSONの意味は、全員がフォーマットに合意するということです。JSON5は人間のためのものであり、マシンのためではありません。

私のルール:ツールがJSONCまたはJSON5をサポートしているなら、使いましょう。設定ファイルにコメントがあることのデメリットは文字通りゼロです。ただし、設定を読み込むライブラリを書いている場合は、まず標準JSONをサポートし、JSONC/JSON5はオプションの追加としてください。

YAMLアンカーとエイリアス:DRY設定

ここからが多くの開発者が発見しないYAMLの真のキラー機能です:アンカーとエイリアス。設定ブロックを一度定義して、どこでも再利用できます。基本的に設定ファイルのためのDRY(Don't Repeat Yourself)です。

アンカーは&名前で定義し、エイリアスは*名前で参照します。実用的なDocker Composeの例を見てみましょう:

yaml

何が起きたかわかりますか?共通設定を&commonで一度定義し、<<: *commonで3つのサービスに取り込みました。アンカーがなければ、リスタートポリシーとロギング設定をすべてのサービスにコピペすることになります。ログローテーションを変更する必要がある時は?12箇所ではなく1箇所だけです。

アンカーはより単純な値にも使えます — マップだけではありません:

yaml

さて、注意点です。アンカーを本当に強力にするマージキー<<は、実はYAML 1.2仕様の一部ではありません。YAML 1.1の型拡張であり、ほとんどの人気パーサーはまだサポートしていますが、技術的には非標準です。厳密なYAML 1.2パーサーを使っている場合、マージキーが動作しない可能性があります。

また、アンカーは単一ファイル内でのみ機能します。別のYAMLファイルで定義されたアンカーを参照することはできません。エイリアス名を間違えた時のエラーメッセージは?通常「undefined alias」のようなもので、アンカーがどこにあるべきだったかのコンテキストはゼロです。典型的なYAMLです。

TOML深掘り:上級機能

ほとんどの人はTOMLの基本を知っています — [括弧]によるセクション、キー・バリューペア、#によるコメント。しかしTOMLには十分に注目されていない本当にクールな機能があります。見ていきましょう。

ドット付きキーはセクションヘッダーなしでネストされた構造を定義できます:

toml

ネストされた値が数個しかなく、わざわざセクションを作りたくない時に便利です。

インラインテーブルは小さなオブジェクト用のJSON風コンパクト構文を提供します:

toml

インラインテーブルは控えめに使いましょう — 複数行にまたがることはできず、詰め込みすぎるとすぐに読めなくなります。

複数行文字列は2つの種類があり、ここでTOMLは驚くほど考え抜かれています:

toml

トリプルクォートの基本文字列(""")はエスケープシーケンスを処理し、リテラル版(''')はすべてを生テキストとして扱います。バックスラッシュをエスケープとして解釈させたくない正規表現パターンやWindowsファイルパスに最適です。

ネイティブの日付/時間型はJSONもYAMLもこれほどクリーンには扱えません:

toml

これらはTOMLのファーストクラス型であり、日付のふりをした文字列ではありません。TOMLパーサーは実際の日付/時間オブジェクトを返し、自分でパースする必要がある文字列ではありません。

なぜRustのCargoはTOMLを選んだのでしょうか?Cargoの設定がまさにTOMLのスイートスポットだからです:適度にネストされ、人間が編集し、コメントが必要で、厳密な型付けの恩恵を受けます。依存関係のバージョンが暗黙的にfloatとして解釈されるのは望ましくないですよね(YAML、君のことだよ)。

セキュリティの考慮事項

さて、ここからは少し怖い話になります。信頼できないソースから設定ファイルを読み込んでいる場合 — あるいはそうしていないと思っている場合でも — デシリアライゼーション攻撃について知っておく必要があります。

その代表例がPyYAMLのyaml.load()です。古いバージョンでは、この無害に見える関数がYAMLファイルに埋め込まれた任意のPythonコードを実行できました。冗談ではありません。これを見てください:

yaml

誰かがこれをYAML設定に忍び込ませ、PythonアプリがTMLyaml.safe_load()ではなくyaml.load()で読み込むと、文字通りそのシステムコマンドを実行します。すべて削除。バックドアをインストール。攻撃者が望むことは何でも。

修正はとても簡単です — Pythonでは常にyaml.safe_load()を使いましょう:

python

PyYAMLのドキュメントは今これについて警告しており、新しいバージョンではLoaderを指定せずにyaml.load()を使うと非推奨警告が表示されます。しかしまだ無数のチュートリアルやStack Overflowの回答が安全でないバージョンを示しています。目の前に隠れている地雷です。

次に「billion laughs」攻撃(XML爆弾とも呼ばれますが、YAMLでも機能します)があります。アイデアは再帰的展開です — 他のエンティティを参照するエンティティを定義し、指数関数的な成長を作り出します:

yaml

各レベルでデータが5倍になるため、レベル8か9では数行のYAMLからギガバイトのデータになります。ほとんどのモダンなYAMLパーサーにはこれを防ぐための深さと展開の制限がありますが、知っておく価値はあります。

JSONは本質的に安全です。なぜなら実行セマンティクスが一切ないからです。コードを埋め込む方法がなく、型コンストラクタもなく、エンティティ展開もありません。JSONパーサーは単にデータを読み取ります — 文字列、数値、ブーリアン、配列、オブジェクト。それだけです。これはJSONのシンプルさの過小評価されている利点の一つです。セキュリティが重要な時、JSONの機能の少なさは実は機能なのです。

TOMLもかなり安全です — コード実行機能も再帰展開もありません。ただしJSONパーサーほど実戦で鍛えられていないので、TOMLライブラリは最新に保ちましょう。

結論:ユーザーや外部ソースから設定ファイルを受け入れる場合、JSONが最も安全な選択です。YAMLを使わなければならない場合は、常に安全なロード関数を使用し、疑わしい構造を検出するためにYAMLリンターの実行を検討してください。

実際の設定ファイル例

理論は素晴らしいですが、実際に遭遇する設定ファイルを見てみましょう。フォーマット固有のパターンを強調するために各ファイルに注釈をつけました。

GitHub Actionsワークフロー(YAML):

yaml

ここでYAMLが本当に輝きます。GitHub Actionsのワークフロー構文はJSONでは苦痛になるでしょう — すべてのネストされたリストとインデントベースの構造はYAMLに自然にマッピングされます。コメントがマトリクス戦略を説明するのにも役立っていることがわかります。

RustのCargo.toml(TOML):

toml

Cargoマニフェストがフィーチャー付きの依存関係にインラインテーブルを使っていることに注目してください。[[bin]]のダブルブラケット構文はテーブルの配列を定義します — 各[[bin]]エントリが別のバイナリターゲットを追加します。TOMLはインデントのトリックなしに、フラットで読みやすくまとめています。

package.json(JSON):

json

定番です。コメントなし、引用符だらけ、でも地球上のあらゆるツールが読めます。JSONの制限にもかかわらずpackage.jsonがうまくいく理由は、スキーマがとても有名だから — scripts.buildが何をするか説明するコメントは必要ありません。すべてのJavaScript開発者がすでに知っているからです。

.prettierrc(JSON):

json

シンプルでフラットなキー・バリュー設定。正直なところ、TOML(コメント!)やJSONCの方がわずかに良いでしょうし、Prettierは他のフォーマットもサポートしています。でもJSONがデフォルトで、これくらいシンプルなものなら大した違いはありません。

フォーマット間の移行

プロジェクトの設定フォーマットが間違いだったと決断して変更したいとします。あるいは別のフォーマットを使うツールから設定を取り込んでいるかもしれません。いずれにせよ、TOML、YAML、JSON間の変換が必要です。言っておきますが、期待するほどスムーズにいかないこともあります。

変換ツール:

  • yq — YAMLのスイスアーミーナイフ。YAML、JSON、TOML、XML間の変換が可能。yq -o=json config.yamlでJSON出力。jqのYAML版です。
  • toml-clitaplo — コマンドラインTOMLプロセッサ。Taploは特にTOMLフォーマットとバリデーションに優れています。
  • Pythonワンライナー — 急場しのぎにpython -c "import yaml, json, sys; print(json.dumps(yaml.safe_load(sys.stdin), indent=2))" < config.yamlでYAMLをJSONに変換。きれいではないですが、動きます。
  • オンラインコンバーター — 単発の簡単な変換には、私たちのフォーマッターが役立ちます。適切なフォーマッターに設定を貼り付け、整理して、ターゲットフォーマットで手動で書き直します。

移行のタイミング:

  • チームがCI設定でYAMLのインデントエラーを繰り返している?TOMLへの切り替えでその悩みが減るかもしれません。
  • JSON設定ファイルにコメントが必要?JSONC(ツールがサポートしている場合)またはTOMLへの移行を検討してください。
  • 新しいRust/Pythonプロジェクトを構築していてエコシステムがTOMLを期待している?逆らわずにTOMLを使いましょう。

移行時のよくある落とし穴:

YAMLの暗黙的な日付解析には注意が必要です。release: 2024-03-08というYAML値がある場合、YAMLはそれを文字列ではなく日付オブジェクトとして解釈します。それをJSONに変換すると、コンバーターによって"release": "2024-03-08T00:00:00Z"または"release": "2024-03-08"になるかもしれません。出力は必ずテストしましょう。

TOMLの厳密な型付けは、型が混在した配列を持てないことを意味します。JSONとYAMLでは[1, "two", true]は完全に有効です。TOMLでは配列のすべての要素が同じ型でなければなりません。ソースデータに混合配列がある場合、再構造化が必要です。

そして大きなポイント:コメントが失われます。JSONはコメントをサポートしないので、TOMLやYAMLからJSONに変換すると、丁寧に書いたコメントがすべて消えます。逆方向(JSONからYAML/TOML)では、明白でない設定を説明するために手動でコメントを追加したくなるでしょう。なぜならJSON版にはコメントがなかったのですから。

もう一つ — YAMLのアンカーとエイリアスにはJSONやTOMLに相当するものがありません。YAML設定がDRY設定のためにアンカーに依存している場合、他のフォーマットへの変換は共有ブロックを手動で複製する必要があることを意味します。複雑な設定ではファイルサイズが大幅に増加する可能性があります。

自分で試してみましょう

どのフォーマットを選んでも、設定を適切にフォーマットしておけば、レビューとデバッグが楽になります。TOML Formatterで乱れたTOMLファイルを即座にきれいにできます。YAML Formatterはインデントの問題を事前に修正します。そしてJSON Formatterは深くネストされたJSON設定でも一目で読めるようにします。