YAMLのNorway問題とエンジニアが知るべきJSON ↔ YAML変換の差異
いつもどおりのHelmデプロイのはずだった。チームはマルチリージョンのロールアウトに向けてvalues.yamlファイルを2日間かけて調整していた。チャートはロケールメタデータを含むKubernetes ConfigMapをテンプレート化していた。ノルウェーのデータセンターのカントリーコードも含まれていた。誰かが country: NO と入力してコミットした。CIパイプラインはグリーンになった。デプロイが実行された。
そしてアラートが来た。
ConfigMapには country: "NO" ではなく country: false が含まれていた。countryフィールドを読み取るすべての下流サービスが文字列ではなくブール値を受け取った。文字列比較が失敗した。ルーティングロジックがデフォルトにフォールスルーした。ノルウェーに留まるはずだったトラフィックが間違ったリージョナルエンドポイントで処理されてしまった。
原因はYAMLファイル内のたった1つの引用符なしの文字列だった。YAML 1.1 — 事実上すべてのKubernetesツールが使用するバージョン — は NO をブール値の false として扱う。YES・ON・OFF・Y・N・no・yes・on・off・y・n、そして数十種類のバリアントも同様だ。警告なし。エラーなし。無言で間違った値になる。
JSONにはこの問題がない。{"country": "NO"} は常に文字列だ。YAMLの暗黙的な型強制はその最大の利便性であると同時に、最も危険な落とし穴でもある。
このガイドでは全体像を説明する:Norway問題が存在する理由・YAML 1.2で何が変わったか(そしてなぜ多くのツールがそれを無視するか)・正しいクォート戦略の書き方・初心者がつまずくインデントルール・数値精度の罠・KubernetesマニフェストからTerraformプランまで4つの実際の変換シナリオ。JSONの値をYAMLにこの罠なく安全に変換する必要があるときは、JSON to YAMLコンバーター がNorway問題の文字列を自動的にクォートします。
JSON対YAML — どちらをいつ使うか
Norway問題に深入りする前に、各フォーマットが実際に何に最適化されているかを理解するとよい。これらは互換性があるわけではなく、それぞれ特定のコンテキストでより良い選択になる設計の中心を持っている。
| 観点 | JSON | YAML |
|---|---|---|
| 構文 | 厳格 — 波括弧・引用符・カンマが必要 | 柔軟 — インデントベース・句読点が少ない |
| 型システム | 明示的:文字列・数値・ブール値・null・配列・オブジェクト | 暗黙的 — YAML 1.1は値の形から型を推論する |
| 人間の可読性 | 開発者フレンドリー、マシン検証可能 | 人間フレンドリー、手で編集しやすい |
| クォート要件 | 文字列は常にクォート | ほとんどのスカラーはクォートなしで可(Norwayの原因) |
| コメント | サポートなし | # でサポート |
| 主な用途 | API・データ交換・モダンな設定システム | Kubernetes・Docker Compose・Ansible・CIパイプライン |
| 意外なパース | なし — 厳格なパース | あり — Norway・8進数・タイムスタンプ |
| スキーマ強制 | JSON Schemaエコシステム | YAMLスキーマ(ツールは少ない) |
JSONが優位なのはデータがシステム境界を越えるとき — REST API・メッセージキュー・データベースシリアライゼーション。マシンがパースし、マシンが生成し、厳格な構文がバリデーションを簡単にする。送信前に JSON整形ツール で構造を検証しよう。
YAMLが優位なのは人間が主な作者のとき。Kubernetesマニフェスト・GitHub Actionsワークフロー・Helmチャート・Ansibleプレイブック — これらはエンジニアが何十回も読んで編集するファイルだ。句読点の少なさとコメントのサポートにより、JSON相当のものよりも本当に保守しやすい。
問題は境界で発生する:ツールがJSON(kubectl get deploy -o json や terraform show -json のような)を生成して、人間がYAMLとして結果をバージョン管理または編集する必要があるとき。この変換がNorway問題の住処だ。逆方向が必要なときは YAML to JSONコンバーター がある。
Norway問題 — 深掘り
Norway問題はバグではない。YAML 1.1仕様が設計通りに動作している機能だ。なぜそのように設計されたか — そしてなぜ多くのシステムが1.1を実装し続けているか — を理解することが、それを避けるための鍵だ。
なぜ “no”・“yes”・“on”・“off”・“y”・“n” が誤ってパースされるか
YAML 1.1仕様は人間フレンドリーであることを意図した幅広いブール型を定義していた。以下のすべてを true または false として認識した:
true: y, Y, yes, Yes, YES, true, True, TRUE, on, On, ON
false: n, N, no, No, NO, false, False, FALSE, off, Off, OFF
意図は良かった:設定ファイルは英語でtrue/falseの代わりにyes/noを使うことが多く、YAMLは人々が設定を書く自然な方法をサポートしたかった。問題は、yes・no・on・off・y・n はまた、ほとんどのアプリケーションでまったく異なる意味を持つ完全に正当な文字列値でもあるということだ。
具体的なYAMLでの不一致:
# YAML 1.1(ほとんどのパーサーが実装)
country: NO # パース結果: country: false ← 危険
enabled: yes # パース結果: enabled: true
restart: off # パース結果: restart: false
language: y # パース結果: language: true
shell: n # パース結果: shell: false
# 正しい — 明示的な文字列クォートが型推論を上書き
country: "NO" # パース結果: country: "NO" ← 安全
enabled: "yes" # パース結果: enabled: "yes"
restart: "off" # パース結果: restart: "off"
language: "y" # パース結果: language: "y"
shell: "n" # パース結果: shell: "n"
JSON比較:
{"country": "NO"}
JSONでは、クォート内の NO は常に無条件に文字列だ。暗黙的な型推論はない。JSONを冗長に感じさせる厳格さが安全にもつながっている。
ブール強制を超えて、YAML 1.1は暗黙的に以下も変換する:
123e4→ 数値1230000(科学的記数法)0x1A→ 数値26(16進数)0755→ 数値493(8進数 — Unixファイルパーミッション文字列を壊す)2024-05-04→ 多くのパーサーで日付オブジェクト(単なる文字列ではない)1_000_000→ 数値1000000(アンダースコア区切り)
Norway問題はYAMLの暗黙的型強制ファミリー全体で最も有名なものに過ぎない。
YAML 1.1対1.2 — 何が変わったか
YAML 1.2は2009年に公開された — YAML 1.1の4年後だ。主な目標はYAMLをJSONと厳密に整合させること(JSONは実際には有効なYAML 1.2のサブセットになっている)と、驚くような暗黙的型変換を減らすことだった。
YAML 1.2では:
- ブール値は
trueとfalseのみに絞り込まれた(大文字小文字を区別する)。それだけだ。yes・no・on・offは普通の文字列だ。 - 8進数リテラルは
0oプレフィックスが必要(0o755)— 旧来の0755形式は文字列になる。 - タイムスタンプは暗黙的にパースされない —
2024-05-04は明示的にタグを付けない限り文字列のまま。 - 仕様自体がJSONのスーパーセットであり、すべての有効なJSONドキュメントが有効なYAML 1.2になる。
紙の上では、YAML 1.2はNorway問題を完全に解決している。実際には、エコシステムはほとんど動いていない。
| ライブラリ | デフォルト仕様 | Norwayリスク |
|---|---|---|
| PyYAML(Python) | YAML 1.1 | あり — yaml.safe_loadはまだNOをFalseとしてパース |
| ruamel.yaml(Python) | YAML 1.2(オプション) | 設定可能 — デフォルトはより安全 |
| js-yaml(Node.js) | YAML 1.1 | 旧バージョンではあり;新バージョンはFAILSAFE_SCHEMAオプション有 |
| eemeli/yaml(Node.js) | YAML 1.2 | なし — デフォルト1.2、またはバージョン選択可能 |
| gopkg.in/yaml.v2(Go) | YAML 1.1 | あり |
| gopkg.in/yaml.v3(Go) | YAML 1.2 | 大幅に安全 |
| Kubernetes / Helm | YAML 1.1(Go yaml.v2経由) | あり — 歴史的、移行が非常に困難 |
| Ansible | YAML 1.1(PyYAML経由) | あり |
移行が遅い理由は後方互換性だ。yes/no をブール値としてパースすることに10年間依存してきたシステムは、既存の設定を壊さずにその動作を無言で変えることができない。Kubernetesは特に巨大なインストールベースを持っており、YAMLパースのセマンティクスを変更するとクラスター全体の破壊的変更になる。
実践的な結論: 明示的に設定していないツールではYAML 1.1セマンティクスを前提とすること。ブール値・タイムスタンプ・数値として誤読される可能性のある文字列は常にクォートすること。
本番システムはどうやってやられるか
ノルウェーのカントリーコードが最もよく引用される例なのは、直感に反するからだ — NO は明らかな略語に見え、ブール値には見えない。しかしこのパターンは多くの実際のシナリオで繰り返される:
IATA空港コード。 ノルウェーの空港Harstad/Narvikのコードは EVE だ。安全。オスロ・ガーデルモン空港は OSL だ。これも安全。しかし空港コードを保存するためにYAMLを使うアプリケーションは、no ルートコードが1つあるだけで本番でブール値falseになる。
環境変数の名前。 ON は一部のレガシーシステムで「有効」を意味する完全に有効な環境変数の値だ。OFF はその対応物だ。シェルスクリプトからYAMLへの設定移行でこれらの値をクォートしないと、暗黙的な型強制が導入される。
メールのユーザーフィールド。 名前やユーザー名が文字通り n・y やトリガーワードのいずれかであるユーザーは、アプリケーションが適切なクォートなしでYAMLをダンプすると正しくシリアライズされない。これは一部のユーザーに対してのみ失敗するため特に陰険だ。
Docker Composeのリスタートポリシー。 restart_policy フィールドの値 "no" は「再起動しない」を意味する。YAMLのラウンドトリップでクォートが外れると、値が false になり、Docker Composeはそれを「再起動ポリシーが指定されていない」として解釈するかバリデーションエラーをスローする — いずれにしてもコンテナの再起動動作が間違いになる。
GitHub Actionsの shell: フィールド。 有効なシェルの値は bash・pwsh・python・sh・cmd・powershell だ。これらはどれもNorwayワードではない。しかしドラフト編集中にプレースホルダーとして shell: yes や shell: on と入力した人は、バリデーターが見る前にYAMLがそれをブール値に変えてしまうことに驚くかもしれない。
すべての場合に修正方法は同じだ:人間がキーワードとして認識するかどうかに関わらず、意味的に文字列である文字列をクォートすること。JSON to YAMLコンバーター はこれを自動的に適用します — Norwayワードリスト内のいずれかの値は出力でクォートされます。
文字列クォート戦略
Norway問題が発生する理由を理解したら、解決策はユースケースに合った正しいクォート戦略を選ぶことだ。YAMLは3つのモードをサポートし、それぞれ異なるトレードオフがある。
自動対ダブル対シングル
自動クォート(ほとんどの変換で推奨)はライブラリがいつクォートが必要かを決めるようにする。クォートなしで誤読される値 — Norwayワード・数値・タイムスタンプ・YAMLシンタックスのように見える文字列 — は自動的にクォートされる。それ以外はプレーンスカラーのまま。これにより安全性を保ちながら最も読みやすい出力が生成される。
# 自動モードの出力
name: Alice # プレーン — 曖昧さなし
country: "NO" # クォート — Norwayワード
age: 30 # プレーン — 明確な数値
created: "2024-05-04" # クォート — そのままでは日付としてパースされる
port: "8080" # ライブラリによる — 一部は数値に見える文字列をクォート
ダブルクォート文字列はすべての文字列値をダブルクォートで囲む。これは明示的で監査可能だ — 任意の読者がspec仕様について推論することなく、これらすべての値が文字列であることがわかる。トレードオフは冗長性と、特に深くネストされた設定での人間の可読性の低下だ。
# ダブルクォートモード
name: "Alice"
country: "NO"
replicas: "3" # 数値でさえ文字列になる — スキーマエラーを引き起こす可能性
注意:対象スキーマが数値を期待しているときにクォートされた文字列としてシリアライズすると、YAMLパーサーはそれを正しく文字列として型付けするが、Kubernetesや別の厳格なコンシューマーがフィールドを間違った型として拒否するかもしれない。
シングルクォート文字列はYAML固有の機能だ — JSONにはシングルクォート構文がない。シングルクォートはリテラルで、内部にエスケープシーケンスがない。唯一の特殊なケースは、シングルクォートされた文字列内のシングルクォートを二重にする必要があること('')。シングルクォートはバックスラッシュや、ダブルクォートではエスケープが必要な特殊文字を含む文字列に最適だ。
# シングルクォートモード
pattern: 'C:\Users\alice\Documents' # エスケープ不要
regex: '\d+\.\d+' # バックスラッシュがリテラル
JSONへのラウンドトリップを意図したJSON to YAML変換には、自動またはダブルモードを推奨する。シングルクォートされた文字列は、帰り道にYAML対応パーサーを必要とするYAML固有の構文を導入する。
ブロックスカラー(| と >)
YAMLのブロックスカラー構文は複数行文字列に本当に役立つ — JSONが \n エスケープシーケンスで不格好に処理するもの。
リテラルブロックスカラー | は改行をそのまま保持する:
# リテラルブロック — 改行を保持
script: |
#!/bin/bash
set -euo pipefail
echo "Starting deployment"
kubectl apply -f manifest.yaml
# 相当するJSON表現(読めない)
# {"script": "#!/bin/bash\nset -euo pipefail\necho \"Starting deployment\"\nkubectl apply -f manifest.yaml\n"}
フォールドブロックスカラー > は行をスペースで結合し、各改行をスペースに変換する(空行は改行になる):
# フォールドブロック — 改行がスペースになる
description: >
このサービスはプラットフォーム全体の
認証を処理します。OAuth2、SAML、
APIキー認証をサポートしています。
# 結果: "このサービスはプラットフォーム全体の 認証を処理します。OAuth2、SAML、 APIキー認証をサポートしています。\n"
ブロックスカラーはTLS証明書・複数行シェルスクリプト・YAMLに埋め込まれたSQLクエリで真価を発揮する — JSON相当は人間が読めない長い1行になる。
JSONをYAMLに変換するとき、ほとんどのコンバーター(私たちのツールを含む)は自動モードを使い、埋め込み改行が検出された場合のみブロックスカラーで複数行文字列を表現する。1行文字列はフロースカラー(クォートまたはプレーン)になる。マニフェストにコミットする前に JSON to YAMLコンバーター で出力を確認しよう。
インデント — 2対4スペース、タブは禁止
YAMLのインデントルールは見た目より厳格だ。仕様には1つの絶対ルールとエコシステムによって異なる1つの慣習がある。
絶対ルール:タブは禁止。 すべてのインデントレベルはスペースを使わなければならない。YAMLファイル内のタブ文字はほとんどのパーサーでパースエラーになる:
# 間違い — タブはパースエラーを引き起こす
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app # ← ここにタブ文字 → ParseError
# 正しい — スペースのみ
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app # ← 2スペース
エラーメッセージはライブラリによって異なる。PythonのPyYAMLでは:
yaml.scanner.ScannerError: while scanning for the next token
found character '\t' that cannot start any token
Goのyaml.v3では:
yaml: line 4: found character that cannot start any token
YAMLファイルにはタブをスペースに展開するようにエディタを設定しよう。VS Codeの場合、ワークスペース設定に追加:"[yaml]": { "editor.insertSpaces": true, "editor.tabSize": 2 }。
慣習:2対4スペース。 どちらも有効だ。エコシステムの慣習は異なる:
| エコシステム | 慣習 | 理由 |
|---|---|---|
| Kubernetesマニフェスト | 2スペース | 公式ドキュメントとサンプルが2を使用 |
| Helmチャート | 2スペース | K8sの慣習に従う |
| Docker Compose | 2スペース | 公式composeスペックのサンプル |
| GitHub Actions | 2スペース | 公式ワークフローのサンプル |
| Ansibleプレイブック | 2スペース | 公式ドキュメント |
| 伝統的な設定 | 4スペース | JSON整形のデフォルトに合わせる |
KubernetesやDocker Composeで使用するファイルには2スペースを使う。人間とカスタムツールのみが読む単独の設定ファイルにはどちらでも — ファイル内で一貫していればよい。JSON to YAMLコンバーター はデフォルトで2スペースインデントを使用し、4スペースを好むプロジェクトのために切り替えられる。
もう1つのルール:子要素は親より多くインデントしなければならないが、追加スペースの数は任意の正の整数(1・2・3・4…)で — ブロック内で一貫している限り。実際には可読性のために常に2または4を使う。
JSON ↔ YAML間の数値処理
両フォーマットとも数値をサポートするが、エッジケースが本番バグを引き起こすほど異なる。
大きな数値の精度損失
JavaScriptの Number 型は64ビットのIEEE 754浮動小数点数だ。整数を2^53 − 1 = 9,007,199,254,740,991まで正確に表現できる。それを超えると整数精度が失われる:
// JavaScriptの精度損失 — これはYAMLの問題ではないが、JSONパースに影響する
JSON.parse('{"v": 9007199254740993}').v
// → 9007199254740992 (3が2になった — 1ビット失われた)
// 安全 — 2^53の範囲内
JSON.parse('{"v": 9007199254740991}').v
// → 9007199254740991 (正確)
これはJavaScript環境でのJSON to YAML変換に影響する。精度はYAMLシリアライゼーションが始まる前にすでに失われているからだ。Kubernetesの metadata.resourceVersion はリソースバージョンが安全な整数範囲を超える可能性があるためわざわざ文字列フィールドにされている。observedGeneration・uid のコンポーネントのように小さな数値に見えるフィールドは比較的安全だが、K8sレスポンスのint64フィールドは潜在的に影響を受ける。
回避策:
- 大きな数値を含む変換パイプラインにはPythonまたはGoを使う — どちらも任意精度の整数をネイティブに処理する。
- Node.jsでは、BigIntをサポートするJSONパーサーを使う:
JSON.parse(text, (_, v) => typeof v === 'number' && !Number.isSafeInteger(v) ? BigInt(v) : v). - 損失なしでラウンドトリップしなければならないフィールドには、ソースで文字列としてシリアライズする。
- 変換されたYAMLをレビューするときは、
resourceVersion・generation・タイムスタンプ由来の値などのフィールドを確認する。
8進数と16進数の特性
YAML 1.1は数値のような文字列を非10進数整数として扱う:
# YAML 1.1のパース上の驚き
permissions: 0755 # 8進数493としてパース、10進数の755ではない
value: 0x1A # 16進数26としてパース、文字列"0x1A"ではない
# YAML 1.2の動作
permissions: 0755 # 整数755のまま(10進数)— 8進数は0oプレフィックスが必要
permissions: 0o755 # 1.1と1.2の両方で8進数493としてパース
# 両仕様で安全 — 先頭ゼロの値はクォートする
permissions: "0755" # 常に文字列"0755"
8進数の罠はUnixファイルパーミッション・先頭ゼロを含むIPアドレスのコンポーネント(一部のネットワークデバイス)・先頭ゼロでパディングされた数値コード(郵便番号・製品コード)で特に危険だ。手でYAMLを書くときはこれらの値を常にクォートするか、コンバーターがクォートすることを確認しよう — JSON to YAMLコンバーター はJSONからの数値文字列を検出してその文字列型を保持する。
実際の変換シナリオ
Norway問題とクォート戦略は、実際の変換シナリオに適用したときに具体的になる。
JSONからのKubernetesマニフェスト
典型的なワークフロー:kubectl get deploy my-app -o json でライブオブジェクトをJSONとして取得する。それをクリーンアップして(status・creationTimestamp・管理フィールドを削除)、gitにYAMLマニフェストとしてチェックインしたい。
ソースJSON(省略):
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"name": "my-app",
"namespace": "production",
"labels": {
"app": "my-app",
"region": "NO"
}
},
"spec": {
"replicas": 3,
"selector": {
"matchLabels": { "app": "my-app" }
},
"template": {
"spec": {
"containers": [{
"name": "app",
"image": "registry.example.com/my-app:v1.2.3",
"env": [
{ "name": "REGION", "value": "NO" },
{ "name": "ENABLE_FEATURE", "value": "yes" }
]
}]
}
}
}
}
Norway保護付きの期待されるYAML出力:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
namespace: production
labels:
app: my-app
region: "NO" # クォート — Norwayワード
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
spec:
containers:
- name: app
image: registry.example.com/my-app:v1.2.3
env:
- name: REGION
value: "NO" # クォート — Norwayワード
- name: ENABLE_FEATURE
value: "yes" # クォート — Norwayワード
replicas: 3 はクォートなしのまま — Kubernetesが数値として期待する正当な整数だ。labels と env の値内のNorwayワードはクォートされている。YAML 1.1のブール値を処理しないナイーブなコンバーターは無言で region: false と value: false を生成してしまう。
変換後に検証する:kubectl apply --dry-run=client -f manifest.yaml。これはクラスターに触れることなくスキーマエラーをキャッチする。
JSON to YAMLコンバーター で変換を試してみよう — 上のJSONを貼り付けるとNorway安全な出力が即座に得られる。ラウンドトリップを検証するには YAML to JSONコンバーター を使おう。
JSONからのDocker Compose
CI/CDパイプラインはJSON設定ストアからDocker Compose設定をプログラム的に生成し、開発者が読めるようにYAMLとしてディスクに書き込むことがある。
重大な罠 — リスタートポリシー:
{"restart_policy": "no"}
Composeでは restart_policy: "no" は「コンテナを再起動しない」を意味する有効な値だ。YAMLでクォートなしだと restart_policy: false になり、Docker Composeはそれを同じセマンティクス(偽 = 再起動なし)として扱うか、型バリデーションエラーで拒否するかのどちらか — 動作はComposeのバージョンによって異なる。クォートは必須だ。
注意も必要: Compose v3の deploy.restart_policy.condition: "on-failure" — on-failure の値には on という語が含まれるが、ハイフンでつながっているためトリガーリストには入っておらず実際は安全だ。しかし condition: on(-failure なし)は不一致になる。environment: ブロックの環境変数の値がNorwayワードになる可能性があればクォートしよう。
変換後にComposeファイルを検証する:docker-compose config がパースして正規の形式に再出力し、型エラーを表面化させる。
GitHub Actionsワークフロー
GitHub Actionsワークフローは開発者が手で編集するYAMLファイルだ。最もよくある変換シナリオは、GitHub API(JSONを返す)からワークフローデータを読み取り、ローカルのYAMLファイルに編集用として変換することだ。
注意すべき主要フィールド:
# 安全 — 標準GitHub ActionsにNorwayワードなし
on: # ここでの"on"はYAMLキー — 値とは異なる扱い
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run tests
run: |
npm install
npm test
env:
NODE_ENV: production # 安全 — Norwayワードではない
DEBUG: "off" # 値にNorwayワード — クォートが必要
注意:YAMLキーとしての on: は特別 — Norway問題は値に適用されるが、キーには適用されない。しかし値としての on(DEBUG: on のような)は強制をトリガーする。env: ブロックは特に精査が必要だ。環境変数の値は文字列だが、多くは短いフラグでNorwayワードと衝突する可能性がある。
shell: 指定を含むワークフローでは、有効な値(bash・pwsh・sh・python)はすべてNorway強制から安全だ。カスタム値は予防的にクォートすべきだ。
Terraform JSON計画 → YAML
terraform show -json tfplan > plan.json でTerraformが作成・変更・削除する計画の詳細なJSON表現を出力する。これをYAMLに変換するとプルリクエストのレビューやコンプライアンス監査で読みやすくなる。
terraform plan -out=tfplan
terraform show -json tfplan > plan.json
# その後ツールまたはライブラリで変換
Terraform計画JSONは複雑で深い。変換時の主な考慮事項:
-
大きな整数ID。 クラウドリソースID(AWSアカウントID・GCPプロジェクト番号)と計算された属性値は大きな数値になることがある。float64精度の損失を避けるためにPythonまたはGoを使って変換しよう。
-
バージョン制約文字列。 Terraformはプロバイダーバージョン制約で
~>・>=・<=を使う。これらはYAMLが文字列として正しく処理する文字列値だが、~>はNorwayワードではないので安全だ。 -
プロバイダー設定値。 Terraform計画の出力にはリソースの設定値が含まれることがある。ブール値フィールドのデフォルトが
falseで一部のプロバイダースキーマで"no"として表現される場合、YAMLへの変換でNorwayリスクがある。 -
.sensitive_valuesブロック。 センシティブな値は計画JSONでtrueブール値として編集される。trueはどちらのYAMLバージョンでもNorwayワードではないので、変換はクリーンだ。
Terraform to YAML変換は主に人間によるレビューのためであり、Terraformにフィードバックするためではない。YAMLマニフェストをTerraformの入力として使わないこと — Terraformのネイティブフォーマットはで、そのJSON入力フォーマットは別途文書化されている。
コードサンプル — 4言語
Node.js(eemeli/yaml + js-yaml)
Node.jsエコシステムには意味のある異なるNorway処理を持つ2つの主要なYAMLライブラリがある:
// eemeli/yaml — 推奨、デフォルトYAML 1.2、Norway安全
import { stringify } from 'yaml';
import { readFileSync } from 'fs';
const jsonInput = readFileSync('input.json', 'utf8');
const data = JSON.parse(jsonInput);
// デフォルト:YAML 1.2 — "NO"は"NO"のまま、ブール強制なし
const yamlOutput = stringify(data);
console.log(yamlOutput);
// region: NO ← 1.2では安全だが、最大互換性のために明示的にクォートする
// YAML 1.1の動作を強制(YAML 1.1をパースするK8s/Helm環境向け)
const yamlForK8s = stringify(data, { version: '1.1' });
// region: 'NO' ← NOはfalseとしてパースされるため自動クォート
console.log(yamlForK8s);
// js-yaml — 広く普及しているが、YAML 1.1セマンティクス、注意なしにはNorwayリスクあり
import yaml from 'js-yaml';
import { readFileSync } from 'fs';
const data = JSON.parse(readFileSync('input.json', 'utf8'));
// デフォルトのdump — NorwayワードがクォートされないかもGo
const unsafe = yaml.dump(data);
// region: NO ← 1.1パーサーで再読み込みするとfalseとして解釈される!
// より安全:カスタムスキーマを使うか強制クォート
const safer = yaml.dump(data, {
schema: yaml.JSON_SCHEMA, // JSONと互換性のある型に制限
noCompatMode: false,
lineWidth: -1,
quotingType: '"',
forceQuotes: false, // JSONスキーマによる必要な場合のみクォート
});
新プロジェクトには eemeli/yaml を推奨する。YAML 1.2デフォルトが安全で、Document APIがクォートの細かい制御を提供し、ラウンドトリップの忠実度が優れている。既に js-yaml を使っているプロジェクトには、JSONと安全な型に制限するために JSON_SCHEMA オプションを使おう。変換前のJSONフィルタリングと変換の詳細については、前処理パターンの jqコマンドラインチートシート を参照しよう。
Python(PyYAML + ruamel.yaml)
Pythonはそれ自体がKubernetesツール・Ansible・データエンジニアリングパイプラインで主要な言語だ — すべてがヘビーなYAMLユーザーだ。
import json
import yaml
import sys
# PyYAML — シンプル・標準的だが、デフォルトはYAML 1.1
with open('input.json') as f:
data = json.load(f)
output = yaml.dump(data, default_flow_style=False, allow_unicode=True)
# country: 'NO' ← PyYAMLはNorwayワードを自動クォートするほど賢い
# しかし全設定で"yes"・"no"(小文字)をクォートするわけではない:
# enabled: 'yes' ← クォートされる
# tag: y ← バージョンによってクォートされるかどうかは異なる
print(output)
import json
import sys
from ruamel.yaml import YAML
# ruamel.yaml — ラウンドトリップ忠実度・YAML 1.2サポート・本番で推奨
yaml_rt = YAML()
yaml_rt.default_flow_style = False
yaml_rt.width = 4096 # 意図しない行の折り返しを防ぐ
yaml_rt.best_map_flow_style = False
with open('input.json') as f:
data = json.load(f)
yaml_rt.dump(data, sys.stdout)
# キー順序を保持し、Norwayワードを正しく処理し、ラウンドトリップでアンカーをサポート
JSON APIレスポンスをYAMLマニフェストに変換するAnsibleとKubernetesの自動化スクリプトには、ruamel.yaml が安全な選択だ。PyYAMLは入力データを制御していてNorwayワードが現れないことを確認済みのシンプルなスクリプトには問題ない。
変換前にJSON5やJSONCの設定ファイル(コメント付き)を使う場合は、まず拡張子を取り除こう — 互換性のあるパーサーについては JSON5とJSONCフォーマットガイド を参照しよう。
Go(gopkg.in/yaml.v3)
GoはKubernetesエコシステム自体の言語だ — kubectl・Helm・Argo・Flux・ほとんどのK8sオペレーターがGoで書かれている。
package main
import (
"encoding/json"
"fmt"
"os"
"gopkg.in/yaml.v3"
)
func main() {
// JSON入力を読み込む
jsonBytes, err := os.ReadFile("input.json")
if err != nil {
panic(err)
}
// JSONを汎用マップにアンマーシャル
var data map[string]interface{}
if err := json.Unmarshal(jsonBytes, &data); err != nil {
panic(err)
}
// YAMLにマーシャル — yaml.v3はYAML 1.2セマンティクスを使用
yamlBytes, err := yaml.Marshal(data)
if err != nil {
panic(err)
}
fmt.Println(string(yamlBytes))
// country: "NO" ← yaml.v3はNorwayワードを正しくクォート
// replicas: 3 ← 整数は整数のまま
// enabled: true ← ブール値はブール値のまま
}
yaml.v3 はNorway安全性において yaml.v2 よりも大幅に改善されている。v2ライブラリはYAML 1.1に従い、NO をクォートなしで書いていた;v3は曖昧な値を正しくクォートする。v2を使う古いGoプロジェクトを保守している場合は、v3にアップグレードしよう — APIはほぼ互換性があり、安全性の向上は移行の価値がある。
Goの構造体(map[string]interface{} ではなく)を使った型安全な変換には、構造体タグを使おう:
type DeploymentLabels struct {
App string `yaml:"app" json:"app"`
Region string `yaml:"region" json:"region"`
}
// "NO"を含む構造体フィールドでのyaml.Marshalは、v3では正しくクォートされる
Bash CLI(yq + jq)
シェルスクリプトやさっと1回の変換には、yq(Mike Farahのバージョン、mikefarah/yq)が1つのコマンドでJSONをYAMLに変換する:
# yqをインストール
brew install yq # macOS
sudo wget -qO /usr/local/bin/yq \
https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64
chmod +x /usr/local/bin/yq # Linux
# JSONファイルをYAMLに変換
yq -P < input.json > output.yaml
# kubectl JSON出力から変換
kubectl get deploy my-app -o json | yq -P > manifest.yaml
# まずjqでフィルタ/変換してからYAMLに変換
kubectl get deploy my-app -o json \
| jq 'del(.status, .metadata.creationTimestamp, .metadata.managedFields)' \
| yq -P > clean-manifest.yaml
jq | yq パイプラインは強力なパターンだ:jq でJSON操作(フィールドのフィルタリング・構造の変換・値のクエリ)を行い、yq -P を最終YAMLシリアライザーとして使う。jq のパターンについては、kubectl や aws の統合を含む30の実際のパターンが載っている jqコマンドラインチートシート を参照しよう。
yqでのNorway注意点: yq(mikefarah)はJSON入力の型を尊重する — JSONの文字列 "NO" はクォート付きのYAML文字列としてシリアライズされる。しかし(JSON入力からではなく)yq で直接YAMLを生成する場合は、Norwayワードの値を明示的にクォートしなければならない。yq 出力後のラウンドトリップを検証するには YAML to JSONコンバーター を使おう。
エッジケースと落とし穴
Norway問題の他にも、JSON ↔ YAML変換には経験豊富なエンジニアがつまずくいくつかのエッジケースがある:
-
複数ドキュメントYAML(
---セパレーター)。 1つのYAMLファイルには---で区切られた複数のドキュメントを含めることができる。JSONには同等の概念がない。複数ドキュメントYAMLをJSONに変換するとき、ほとんどのツールは最初のドキュメントのみを取る・すべてのドキュメントを配列にマージする・エラーを出すのいずれかだ。複数ドキュメントファイルに遭遇する可能性があるパイプラインでは動作を決定して文書化しよう。 -
YAMLのアンカーとエイリアス。 YAMLはDRYな設定のために
&anchor定義と*alias参照をサポートする。YAMLをJSONに変換するとき、アンカーを展開しなければならない — 結果のJSONはソースYAMLよりはるかに大きくなることがある。JSONをYAMLに変換するとき、コンバーターは元々存在しなかったアンカーを再構築できない。エイリアスはYAML固有の機能だ。 -
タイムスタンプの暗黙的パース。 YAML 1.1パーサーは
2024-05-04と2024-05-04T12:00:00Zを文字列ではなく言語ネイティブの日付オブジェクトに変換する。この日付オブジェクトがJSONに再シリアライズされると、出力はライブラリによって異なる:ISO文字列を出力するもの・Unixタイムスタンプを出力するもの・nullを出力するものがある。明示的な文字列クォート("2024-05-04")なしでYAMLを通じて日付をラウンドトリップすると、フォーマットが無言で変わることがある。 -
!!binaryタグ。 YAMLは!!binaryタグでbase64エンコードされたバイナリデータを埋め込むことができる。JSONにはバイナリ型がない — バイナリはbase64文字列でなければならない。!!binaryフィールドを持つYAMLをJSONに変換するとき、base64文字列にデコードする。戻すとき、スキーマを知らずにバイナリタグを再構築できない。Kubernetesは一部のシークレット値に!!binaryを使う。 -
キーの型衝突。 JSONはオブジェクトキーが文字列でなければならない。YAMLはあらゆる型のキーを許可する — 整数キー・ブール値キー・さらには複雑なオブジェクトキーまで。
true: valueや1: valueをキーとして持つYAMLファイルはJSONで忠実に表現できない。ほとんどのコンバーターはキーを文字列化するが、セマンティクスが変わる。 -
Null表現のバリエーション。 YAMLでは
null・~・Null・NULL・空の値すべてがnullを意味する。JSONではnullのみがnullだ。YAMLをJSONに変換するとき、これらはすべてnullに正規化される。しかしJSONをYAMLに変換するとき、null表現の選択が重要だ —~はより短く、nullはより明示的だ。1つを選んで一貫して使おう。 -
ソート順の変更。 JSONオブジェクトには技術的に定義されたキー順序がない(ほとんどのパーサーは挿入順を保持するが)。YAMLマッピングも同様に必須の順序がない。しかし一部のYAMLライブラリはシリアライズ時にデフォルトでキーをアルファベット順にソートする。ソースJSONが異なる順序を使っていた場合、バージョン管理で大きなdiffを引き起こすことがある。PyYAMLでは
sort_keys=Falseを設定しよう(default_flow_style=Falseだけではソートを防がない)と他のライブラリの同等のオプションを設定しよう。
変換すべきでないとき
変換が常に正しい答えとは限らない。元のフォーマットのままにするのがより良い選択のシナリオがある:
ビジネスロジックを文書化するコメントを含むYAMLをJSONに変換しないこと。 YAMLのコメントはデータモデルの一部ではなく、JSONへのシリアライゼーションで消える。Kubernetesマニフェストに特定のリソース制限が選ばれた理由やセキュリティポリシーの例外が設けられた理由を説明するコメントがある場合、JSONに変換するとそのドキュメントが破棄される。YAMLを保持しよう。
ラウンドトリップテストなしにCIパイプラインの設定を自動変換しないこと。 パイプラインがJSONをYAMLに変換してからクラスターに適用する場合は、ラウンドトリップ検証ステップを追加しよう:YAMLをJSONに戻して、オリジナルと比較する。これにより本番に達する前に型強制の驚きをキャッチできる。
ツールがJSONを出力するからといって変換しないこと。 kubectl・aws・terraform・docker inspect はすべてJSONを出力するが、これらのツールのほとんどはYAMLも入力として受け付ける。変換ステップを構築する前に、対象ツールがYAML入力を直接受け付けられないか確認しよう — ほとんどのモダンなDevOpsツールは受け付けられる。YAML to JSONコンバーター は特にYAMLを受け付けないツールのためにJSONが必要なときに最も役立つ。
スキーマが異なる場合は変換しないこと。 JSONが camelCase キーを使ってYAMLコンシューマーが snake_case を期待する場合(またはその逆)、フォーマット変換に加えて変換ステップが必要だ。単なるフォーマット変換は構文的に正しいが意味的に間違ったYAMLを生成する。スキーママッピングを明示的に対処しよう。
config.json と config.yaml を手動で同期させないこと。 同等であるべき config.json と config.yaml を保守している場合、ずれが生じる。1つの標準的なフォーマットを選び、もう一方を自動的に派生させよう — またはより良いのは1つのフォーマットを選んで重複を排除することだ。
FAQ
Norway問題は現代のシステムに今も影響しますか?
はい — エコシステムに蔓延しています。KubernetesとHelmはコードベースの重要な部分でGoの yaml.v2 ライブラリ(YAML 1.1セマンティクス)を使用しています。AnsibleはPyYAML(YAML 1.1)を使っています。GitHub Actionsワークフローは独自の動作を持つGitHub内部のYAMLパーサーによって解析されます。世の中のほとんどのCI/CD YAMLファイルはYAML 1.1パーサーで処理されます。明示的に確認していない限りは1.1セマンティクスを前提にしてください。
YAMLの方がパースが難しいのにJSONをYAMLに変換するのはなぜですか?
変換はパーサーの難しさについてではありません — 人間の編集可能性についてです。JSONはマシンに最適で、YAMLは設定ファイルを読んで編集してレビューする必要がある人間に最適です。gitにチェックインされ、プルリクエストでレビューされ、エンジニアが手で調整するKubernetesマニフェストはYAMLであるべきです。プログラム的な処理のためにAPIから取得された同じマニフェストはJSONであるべきです。JSON to YAMLコンバーター が橋渡しをします。
JSON ↔ YAMLは損失なくラウンドトリップできますか?
注意点はありますが、JSON互換のデータについてはできます。JSONはYAML 1.2のサブセットなので、すべての有効なJSONドキュメントは有効なYAML 1.2です。JSON → YAML → JSONは暗黙的な型強制のないデータについては損失がないはずです。Norway問題はJSONの文字列 "NO" がコンバーターがクォートした場合のみ往復で生き残り、YAML 1.2パーサーがクォートを尊重した場合のみ帰りも生き残ることを意味します。損失のないラウンドトリップを保証するために両方向でYAML 1.2ライブラリを使いましょう。
本番環境で最も安全なYAMLライブラリは何ですか?
Pythonの場合:YAML 1.2向けに設定された ruamel.yaml。Node.jsの場合:eemeli/yaml(npmの yaml パッケージ)。Goの場合:gopkg.in/yaml.v3。3つすべてがYAML 1.2セマンティクスを実装するか明示的なYAML 1.2モードを持ち、Norwayワードを正しく処理します。新プロジェクトではYAML 1.1ライブラリを避けましょう。互換性の理由でYAML 1.1ライブラリ(PyYAML・js-yaml・yaml.v2)を使わなければならない場合は、常にNorwayが発生しやすい文字列に明示的にクォートしましょう。
JSON変換後のKubernetesマニフェストYAMLはコメントをサポートしますか?
いいえ — コメントはJSONから回復できません。JSONにはコメント構文がないため、変換するものがありません。kubectl get deploy -o json を実行してYAMLに変換してgitに保存すると、結果のYAMLにはコメントがありません。Kubernetesマニフェスト内のコメントは変換後に人間が書かなければなりません。これが手書きのYAMLを標準的なソースとして保持することが、JSON APIを通じてラウンドトリップするよりもしばしば好ましい理由の1つです。
resourceVersionやナノ秒タイムスタンプのような大きな整数をどう処理すればいいですか?
Kubernetesの metadata.resourceVersion はわざわざ文字列フィールドになっています — KubernetesチームはJavaScriptやその他のfloat64ベースのランタイムのJSONパーサーが大きな整数の精度を失うことを知っていました。常に文字列として扱ってください。真に数値的な大きな整数(一部のトレーシングシステムのナノ秒エポックタイムスタンプのような)には、Pythonの int 型・Goの int64・Node.jsの BigInt をパースに使いましょう。カスタムリバイバー関数なしにJavaScriptの JSON.parse() に通さないこと。YAMLに変換するとき、これらの大きな整数は安全です — YAMLには整数の精度制限がありません。危険はJavaScriptのJSONパーサーを通じたラウンドトリップにあります。
YAML 1.2はまだ広く採用されていますか?
まちまちです。主要な言語ライブラリは移行してきています:GoのYAML v3・PythonのRuamel.yaml・Node.jsのeemeli/yamlはすべてYAML 1.2をサポートするかデフォルトにしています。しかしKubernetes・Ansible・多くのDevOpsエコシステムは後方互換性コストのために依然としてYAML 1.1パーサーで動いています。新プロジェクトでのYAML 1.2採用は推奨されますが、自分で設定していないシステムについては1.1を前提にしてください。
チームの設定はJSONとYAMLのどちらを標準化すべきですか?
フォーマットではなく目的で標準化しましょう。コードが使用する設定(APIリクエストボディ・SDK設定ファイル・プログラム的なツール)にはJSONを使いましょう。人間が使用する設定(Kubernetesマニフェスト・CIパイプライン・デプロイ設定・Ansibleプレイブック)にはYAMLを使いましょう。同じ設定で2つを混在させるのを避けましょう — 設定タイプごとに1つの表現を選んで、両方が必要な場合は変換を自動化しましょう。変換が必要なときは、JSON to YAML と YAML to JSON の両コンバーターが完全にブラウザ内で動作します — データがデバイスを離れることはありません。
今すぐ試してみよう
実際のファイルを変換する準備はできていますか?JSON to YAMLコンバーター でJSONを安全なKubernetes YAMLに変換してみましょう — NorwayワードをすべてYAML 1.1のブール値リストから自動クォートし、2スペースまたは4スペースインデントを選べます。逆方向には YAML to JSONコンバーター がアンカー・エイリアス・複数ドキュメントYAMLを処理します。どちらのツールも完全にブラウザ内で動作します — データがデバイスを離れることはなく、センシティブなリソース設定を含む本番KubernetesマニフェストやTerraformプランを扱うときも安心です。