JSON vs YAML: いつどちらを使うか、そしてそれぞれの落とし穴

JSON と YAML は同じデータをモデル化しますが、失敗の形は鋭く異なります。YAML の型強制と空白の罠 vs JSON のコメントの欠如と冗長さを整理します。

JSON と YAML は同じ形を記述します。マップ、リスト、スカラーです。そして両者の選択 はたいていスタイルの好みとして語られます。違います。両者は異なる失敗の形を持ち、 その失敗は別々の場所に落ちます。JSON のものは編集時に噛みつき、YAML のものはパース 時に静かに噛みます。この記事は、各形式がどこで真価を発揮するか、並べた仕組み、そして 動いている設定を午前 2 時のインシデントに変える落とし穴を扱います。

関係

YAML 1.2 は実用上、JSON の上位集合です。すべての有効な JSON ドキュメントが有効な YAML ドキュメントでもあります。YAML の仕様が JSON 構文を部分集合として採用したから です。YAML パーサはこれをそのまま読みます。

{"service": "api", "replicas": 3, "ports": [80, 443]}

それは聞こえるより重要です。YAML が JSON のデータモデルをまるごと受け継ぐという ことです。JSON が表現できて YAML ができないものはありません。違いは形式が保持できる ものではなく、人がそれをどう書くか、そしてパーサが YAML の緩い文法が招くあいまいさを どう解釈するか、にあります。

並べて

同じドキュメントを、まず JSON で見るとこうです。

{
  "service": "api",
  "replicas": 3,
  "image": "registry.example.com/api:1.4.0",
  "ports": [80, 443],
  "env": {
    "LOG_LEVEL": "info",
    "REGION": "us-east-1"
  }
}

そして YAML で見るとこうです。

# api サービス用のデプロイ設定
service: api
replicas: 3
image: registry.example.com/api:1.4.0
ports:
  - 80
  - 443
env:
  LOG_LEVEL: info
  REGION: us-east-1

YAML 版は波括弧、角括弧、キーとほとんどの文字列の周りの引用符、カンマを落とし、 コメントを足します。構造は句読点ではなくインデントで運ばれます。設定を手で編集する 人にとって、これは本当に快適です。落とし穴は、その便利さのすべての断片が、パーサが 推測しなければならない場所でもあり、その推測こそ物事が狂う場所だということです。

機能比較

機能 JSON YAML
コメント なし(仕様が禁止) あり(#)
末尾カンマ なし(仕様が禁止) 該当なし(カンマなし)
複数行文字列 エスケープ \n のみ ブロックスカラー(`
アンカー / エイリアス なし あり(&, *, <<)
パースの厳格さ 高い(単一の文法) 低い(文脈依存、バージョン依存)
型推論 なし(明示的) 積極的(暗黙的)
普遍性 普遍 広いがむら
攻撃面 最小 アンカー、カスタムタグ、深い入れ子

YAML の落とし穴

ここが覚える価値のある部分です。失敗が静かだからです。

ノルウェー問題

YAML 1.1 は素の単語の長いリストを真偽値として扱います。yesnoonofftruefalseyn です。広くデプロイされた多くのパーサが今も 1.1 の挙動を 既定にします。だからこれは、

country: NO

NO を文字列 "NO"(ノルウェーの国コード、それが名前の由来)ではなく真偽値 false としてパースします。同じ種類のバグです。

version: 1.0        # 浮動小数 1.0 としてパース、末尾の 0 が落ちる
build: 1.20         # 1.2 になる
zip: 02134          # 先頭の 0: 1.1 では 8 進数、または 2134 に剥がれる
git_sha: 1234567    # 全部数字の SHA の接頭辞が整数になる
mac: 12:34:56       # 1.1 がコロンを 60 進数として読む
enabled: off        # 文字列 "off" が false になる

郵便番号、git の SHA、バージョン文字列、MAC アドレス、電話番号、シリアルのように、 数字に見えるが意味上は文字列であるものは、すべて静かな型強制の候補です。解は機械的 です。引用符で囲んでください。country: "NO"version: "1.0"zip: "02134"。 引用符はパーサのバージョンによらずスカラーを文字列に強制します。その先では、YAML 1.2 /「コアスキーマ」で構成したパーサを選んでください。真偽値の集合を true/false だけに狭め、60 進数と 8 進数の罠を取り除きます。

意味を持つ空白

インデントが構造なので、インデントの誤りは構造の誤りです。タブは YAML でインデント にまったく許可されません。変換しなかったエディタから来たさまよいのタブはハードな パースエラーで、メッセージはめったに本当の行を指しません。ブロックを別のインデントの 文脈にコピー&ペーストすると意味が変わります。2 つの空白を失ったリスト項目は、静かに かつての親の子ではなく兄弟になります。このどれも、構造が「有効」であることでは捕らえ られません。きちんとパースされ、ただ意図したものと別のドキュメントになるだけです。

アンカー、エイリアス、マージキー

YAML はノードを一度定義して再利用させます。

defaults: &defaults
  retries: 3
  timeout: 30

prod:
  <<: *defaults
  timeout: 60

&defaults がマップをアンカーし、*defaults がそれを参照し、<< がそれをマージ します。これは DRY な設定に本当に便利です。そして、ファイルがそれに重く頼ると可読性 の崖でもあります。キーの実効値が今やドキュメントの別の場所に宿るからです。さらに 悪いことに、再帰的なエイリアスは「billion laughs」サービス拒否攻撃を可能にします。 それぞれが前のものを参照するひと握りの入れ子のアンカーが指数的に膨張し、パース時に メモリを枯渇させます。膨張を制限しないパーサは脆弱です。信頼できないソースから YAML を受け取るなら、エイリアス展開の制限があるパーサ(または「safe」ローダー)を使い、 アンカーをまるごと無効にすることを検討してください。

型強制、一般に

貫く主題は、YAML が型を推論して助けようとし、推論は推測である、ということです。防御 はどの場合も同じです。概念的に文字列であるものはすべて引用符で囲み、寛容な既定では なく厳格な 1.2 のパーサでロードしてください。Python では yaml.load ではなく yaml.safe_load であり、ほかの生態系ではライブラリが公開するスキーマや厳格モードを 探してください。

JSON の限界

JSON の厳格さは、パース時にほとんどあなたを驚かせない理由です。しかし同じ厳格さが、 それを人が保守するには悪い形式にします。

  • コメントなし。 仕様にありません、それだけです。これが JSONC(VS Code の設定)、 JSON5、そして // コメント のハックが存在する理由です。注釈できない設定は、未来の あなたが安全に変えられない設定です。
  • 末尾カンマなし。 配列やオブジェクトに行を足すと、上の行にカンマを足すことを 覚えていなければなりません。これが手で編集する JSON の最もよくある誤りです。
  • 手編集に冗長。 波括弧、角括弧、引用符付きのキーは、人が編集者のときノイズ です。設定の規模では積み上がります。
  • ネイティブの日付やコメント型なし。 日付は慣習上の文字列で、それについての メタデータを運びません。

このどれも機械対機械のトラフィックには問題になりません。そこでは誰もペイロードを手で 編集しません。人が毎週エディタで開くファイルには甚大に重要です。

いつどちらを使うか

分割は失敗の形に従います。機械がデータを生産・消費し、人がほとんど触れないところでは どこでも JSON を使ってください。API のリクエスト・レスポンスの本文、サービス間の交換、 ログレコード、プログラムで直列化される何でもです。厳格さがそこでは機能です。ちょうど 1 つの解釈が欲しく、スキーマがドキュメントなのでコメントが要りません。

人が手で編集する設定には YAML を使ってください。CI パイプライン、Kubernetes の マニフェスト、Ansible のプレイブック、アプリケーションの設定です。コメント、複数行 文字列、軽い文法が、まさに人がループの中にいるところで真価を発揮します。

居心地の悪い注意点は、YAML の落とし穴がまさにこの場合に最も強く噛みつくことです。YAML が輝く人編集の設定ファイルが、引用符のない NO、タブ、間違ってインデントされたリスト 項目がレビューをすり抜けて出荷される、まさにそのファイルです。だから YAML を安全に する規律、つまり文字列らしいスカラーに引用符を付けること、CI でインデントをリント すること、スキーマで検証することは、YAML が最も得意とするワークロードに対して妥協 できません。

その検証の段階はパイプラインに組み込む価値があります。スキーマが yaml.load が決して 警告しない強制のバグを捕まえます。私たちは JSON Schema で検証する で仕組みを扱い、それは いったんパースされればどちらの形式にも働きます。

2 つの形式の間でドキュメントを移すなら、つまり JSON の API フィクスチャを YAML の設定に 移植したり、マニフェストをツールが要求する JSON へ平坦化し直したりするなら、私たちの JSON ⇄ YAML 変換器 が往復を処理して構造を保ち、 JSON フォーマッタ がコミット前に JSON 側をリントして整形します。