YAMLをJSONに変換する方法:よくある落とし穴と回避策
YAMLとJSONが見た目ほど交換可能ではない理由
YAMLとJSONは見た目が似ていて、関係も密接です。YAML 1.2はJSONのスーパーセットですらあり、有効なJSONはすべて有効なYAMLでもあります。素晴らしいことのように聞こえますよね?ええ、実際にYAMLファイルを変換して、最初の「サイレントなデータ破損」に遭遇するまでは。この2つのフォーマットは、設計思想が根本的に異なります。JSONは機械のために作られました。厳格で、曖昧さがなく、コメントの余地もありません。一方、YAMLは人間のために作られました。構造にはインデントを使い、複数行文字列をサポートし、インラインコメントを許可し、こちらの意図を推測しようとする型推論システムを備えています。この「親切な推測」こそが、変換がうまくいかなくなる原因なのです。YAMLパーサーは'yes'という文字列を読み取って、ブール値の`true`と解釈するかもしれません。'1.0'を見て、入力した文字列ではなく浮動小数点数を生成するかもしれません。これらはバグではありません。YAMLの仕様通りに動作しているのです。問題は、JSONにはそのような曖昧さがないために起こります。YAMLの値が一度パースされたデータ内でブール値になってしまうと、JSONの出力には`true`と書き込まれ、元の文字列は永久に失われます。KubernetesクラスタやOpenAPI仕様、CI/CDパイプラインの設定ファイルを変換する際に、こうした静かな型の変更は、エラーメッセージを一切出すことなく、後続のすべての処理を破壊する可能性があります。ファイルを確実に変換するためには、これらの根本的な違いを理解しなければなりません。
最も手軽な変換方法:CocoConvertを使う
とにかくファイルを変換したいだけなら、スクリプトをその場しのぎで書くよりも、専用ツールを使うのが一番手っ取り早いです。CocoConvertの[YAMLからJSONへのコンバーター](/convert/yaml-to-json)は、すべてのパースとシリアライズを管理し、適切にフォーマットされたUTF-8エンコードの出力をすぐに提供します。手順は非常にシンプルです。YAMLを貼り付けるか、.yamlまたは.ymlファイルをアップロードして、「変換」をクリックするだけです。JSONが出力パネルに表示され、コピーやダウンロードがすぐにできます。CocoConvertはモダンなYAML 1.2のパースルールを使用しているので、'NO'という文字列がブール値の`false`と誤解釈される古い「ノルウェー問題」に悩まされることはありません。もし元のYAMLにインデントミスがあれば、何も言わずに壊れたデータを出力するのではなく、行番号付きの明確なパースエラーが表示されます。また、複数ドキュメントを持つYAMLファイル(`---`区切りで書かれたもの)も正しく処理します。これらはJSON配列に変換され、各ドキュメントが配列の要素になります。これは標準的で期待される動作ですが、ファイルが`---`で始まっているために出力に予期せぬ配列が表示された場合は、このことを覚えておくと良いでしょう。1つだけ制限があります。このツールは、同じファイル内の異なるドキュメントにまたがってノードを参照するYAMLアンカーとエイリアスをサポートしていません。このようなドキュメントをまたぐアンカーを含む複雑なケースでは、変換のためにファイルをアップロードする前に、手動かローカルのスクリプトでそれらを解決しておく必要があります。
YAMLの型強制:一番ハマりやすい落とし穴
型強制は、YAMLからJSONに変換する際のデータ損失の最大の原因です。本番用のファイルを変換する前には、これらの特定の落とし穴がないか絶対に確認しなければなりません。 **予期せぬ文字列からのブール値。** 古いYAML 1.1パーサー(バージョン6.0以前のPyYAMLなど)は、`yes`、`no`、`on`、`off`をブール値として解釈していました。モダンなYAML 1.2では`true`と`false`のみがそのように扱われますが、もし元のファイルが古いツールで作成されたものなら、本当は文字列'yes'を意味するのに'yes'が含まれているかもしれません。ファイルの出所がわからない場合は、これらの値を手動でチェックする必要があります。 **8進数。** これは古典的な問題です。YAMLでは、`0755`のような値は8進数の整数493としてパースされます。これは、Kubernetesマニフェストでファイルパーミッションを設定する際の悪名高い罠です。変換されると、JSONには文字列の`'0755'`ではなく、数値の`493`が含まれることになります。もし後続のプロセスがその数値を`chmod`呼び出しで使おうとすると、パーミッションは完全に間違ったものになり、しかもエラーは出ません。 **浮動小数点数のエッジケース。** YAMLは`.inf`、`-.inf`、`.nan`のような特殊な浮動小数点数を理解しますが、JSONは理解しません。CocoConvertはこれらを文字列の'Infinity'、'-Infinity'、'NaN'に変換することで対応します。これは賢明な代替策ですが、もしアプリケーションが数値のみを期待している場合、これらの文字列値で失敗し、後処理が必要になるかもしれません。 **nullの表現。** YAMLはnullの扱いに柔軟で、`null`、`~`、あるいはキーの後に値がない場合も受け入れます。これらはすべて、JSONでは標準の`null`になります。これは通常問題ありませんが、コロンの後に何もないキーは、空文字列`""`ではなくJSONの`null`になることを覚えておいてください。
複数行文字列とコメントの扱い
YAMLには、JSONに直接の相当物がない2つの強力な複数行文字列構文があります。リテラルブロックスカラー(`|`)と畳み込みブロックスカラー(`>`)です。リテラルブロック(`|`)はすべての改行を保持します。畳み込みブロック(`>`)は単一の改行をスペースに変えますが、二重の改行は実際の改行として保持します。どちらの構文も単一のJSON文字列を生成しますが、シェルスクリプト、SQLクエリ、証明書のような埋め込みコンテンツにとっては、改行処理の微妙な違いが非常に重要になります。 例えば、次のYAMLは: ```yaml script: | echo hello echo world ``` このJSONになります: ```json {"script": "echo hello\necho world\n"} ``` リテラルスタイルの`|`では、末尾の改行(`\n`)がデフォルトで保持されることに注意してください。これを取り除くには、チョンピングインジケータ`|-`を使います。微妙な空白の違いでCIスクリプトが失敗するのをデバッグしたことがある人なら、この苦労がわかるでしょう。これを間違えると、空白に敏感なスクリプトやAPIが壊れる可能性があります。 コメントはもっと厄介な問題です。YAMLは`#`を使ったコメントをサポートしますが、JSONはサポートしません。以上です。つまり、変換中にYAMLファイル内のコメントは一つ残らず永久に削除されます。特定の値がなぜそのように設定されているのかを説明する重要なコンテキスト(Infrastructure-as-Codeではよくある慣習です)は、すべてJSON出力から消え去ります。JSONの仕様内での回避策はありません。私のおすすめはシンプルです。常にコメント付きのYAMLを信頼できる情報源(source of truth)として扱い、生成されたJSONは使い捨てのビルド成果物と見なすことです。JSONC(コメント付きJSON)を使うチームもありますが、それは問題を先送りにするだけです。
アンカー、エイリアス、マージキー
YAMLのアンカーとエイリアスは、ファイルをDRY(Don't Repeat Yourself)に保つための素晴らしい機能ですが、JSONへの変換時には複雑さをもたらします。アンカーは`&anchor-name`で定義し、`*anchor-name`で参照します。YAMLパーサーはファイルを読み込む際にこれらのエイリアスを展開し、メモリ内で最終的なデータ構造を構築します。そのため、JSON出力には完全に展開され、複製されたコンテンツが含まれ、元のアンカーの痕跡は残りません。 この一般的なパターンを考えてみましょう: ```yaml defaults: &defaults timeout: 30 retries: 3 production: <<: *defaults host: prod.example.com staging: <<: *defaults host: staging.example.com ``` `<<`構文はYAMLのマージキーです。結果として得られるJSONは次のようになります: ```json { "defaults": {"timeout": 30, "retries": 3}, "production": {"timeout": 30, "retries": 3, "host": "prod.example.com"}, "staging": {"timeout": 30, "retries": 3, "host": "staging.example.com"} } ``` 展開は正しいですが、元のYAMLの簡潔さは失われてしまいます。もし50のサービスがそのdefaultsアンカーから継承していたら、JSONにはそのデータが50個コピーされることになります。機械にとっては全く問題ありません。しかし、ファイルを読もうとする人間や、ファイルサイズが懸念されるシステムにとっては、これは大きな欠点です。 マージキー(`<<`)のサポートは、技術的にはYAMLの拡張機能であり、コア仕様の一部ではないため、一部の厳格なパーサーはこれを拒否することに注意してください。CocoConvertはマージキーを問題なく処理します。PythonのPyYAMLで変換をスクリプト化する場合、`yaml.full_load()`または`yaml.safe_load()`を使用しなければなりません。古い`Loader`引数なしの`yaml.load()`は、重大なセキュリティリスクのためPyYAML 5.1以降非推奨となっているので避けてください。
プログラムによるYAMLからJSONへの変換
大量の変換、ビルドパイプラインへの統合、その他あらゆる種類の自動処理には、コマンドラインまたはスクリプトによる解決策が必要になります。Webツールは一回限りの作業には最適ですが、自動化にはコードが必要です。以下に、最も信頼性の高い方法をいくつか紹介します。 **Python(最もポータブルな選択肢):** ```python import yaml, json, sys with open(sys.argv[1], 'r') as f: data = yaml.safe_load(f) print(json.dumps(data, indent=2, ensure_ascii=False)) ``` 常に`yaml.safe_load()`を使用してください。古い`yaml.load()`は、悪意のあるYAMLファイルから任意のコードを実行できてしまう、セキュリティ上の悪夢です。`ensure_ascii=False`引数も良い習慣です。Unicode文字をエスケープせずに保持してくれます。 **Node.js:** ```javascript const yaml = require('js-yaml'); const fs = require('fs'); const data = yaml.load(fs.readFileSync(process.argv[2], 'utf8')); console.log(JSON.stringify(data, null, 2)); ``` `js-yaml`ライブラリは、デフォルトで(v4.0以降)モダンなYAML 1.2ルールを使用します。古いプロジェクトで作業している場合は、`package.json`を再確認してください。4.0より前のバージョンはYAML 1.1ルールを使用し、'yes'や'no'のような文字列を誤ってブール値に強制変換してしまいます。 **yq(コマンドラインツール):** ```bash yq -o=json eval '.' input.yaml > output.json ``` 率直に言って、コマンドラインでこの作業を行うなら`yq`が最高のツールです。これはYAML処理専用に作られたツールで、複数ドキュメントファイル、アンカー、マージキーなどすべてを正しく処理し、JSON出力用のシンプルなフラグも備えています。macOSならHomebrewで(`brew install yq`)、Linux/WindowsならGitHubからバイナリを入手してインストールできます。 もちろん、何もインストールせずに手早く変換したいなら、[CocoConvertのYAMLからJSONへのツール](/convert/yaml-to-json)が依然として最速の方法です。
使用前の出力のバリデーション
ファイルを変換してバリデーションしないのは、本番環境に厄介なバグを仕込むようなものです。JSONファイルは構文的には完全に有効でも、これまで説明してきた型強制のように、意味的には間違ったデータを含んでいる可能性があります。将来の頭痛の種をなくすための、実践的なチェックリストを以下に示します。 **構文バリデーション。** 最低でも、出力をJSONリンターに通してください。お使いのコードエディタ(VS CodeやJetBrains IDEなど)は、おそらくこれを自動的に行っているでしょう。コマンドラインからは、Pythonの組み込み`json.tool`が頼りになるツールです:`python3 -m json.tool output.json > /dev/null`。有効なJSONの場合は終了コード0で終了し、失敗した場合はどこで壊れたかを正確に教えてくれます。 **スキーマバリデーション。** 重要なファイルには、スキーマを使いましょう。ターゲットのフォーマットにJSONスキーマがある場合(OpenAPI仕様、AWS CloudFormation、Kubernetes CRDなどでは一般的です)、それに対してバリデーションを行います。`ajv-cli`のようなツール(`ajv validate -s schema.json -d output.json`)を使えば、単純な構文チェックでは見つけられない型の不一致を検出できます。 **既知の正常なバージョンとの差分比較。** 参照となるJSONファイルがある場合、差分比較は不可欠です。しかし、その前に、ノイズの多い無意味な差分を避けるためにキーの順序を正規化しましょう。`jq`ツールを使えば、キーを決定論的にソートできます:`jq --sort-keys . output.json > normalized.json`。JSONではキーの順序は重要ではありませんが、ファイルを比較しようとするときには、これが原因でイライラさせられることになります。 **型強制された値のスポットチェック。** もしYAMLに'1.0'や'0755'のような値があったと疑われる場合は、JSON出力を直接チェックしてください。`grep -n "0755" output.json`をさっと実行すれば、8進数の文字列が変換を生き延びたか、それとも役に立たない整数に変わってしまったかがすぐにわかります。 本当に、コミットやデプロイの前に5分かけて出力を検証する方が、文字列であるはずだったブール値が原因で発生した本番環境のインシデントをデバッグするより、常に速いのです。