JSON文字列化ツール
入力した内容が保存・記録・共有されることはありません。
JSONをコードに貼り付けられるエスケープ済み文字列に変換したり、エスケープされた文字列を読みやすいJSONに戻したりできます。
使い方
- 「文字列化」または「復元」を選びます。
- 入力欄にJSONを貼り付けます。
- コピーボタンで結果をコピーします。
エスケープが実際に行うこと
文字列化では、JSONを解析して圧縮し、その結果をダブルクォートで囲んで、文字列を途中で終わらせてしまうものをすべてエスケープします。内部のダブルクォートは\"に、バックスラッシュは\\に二重化され、U+0020未満の制御文字は短縮形の\n、\r、\t、\b、\fに、短縮形がないものは\u00XX形式になります。
意図的にそのままにしているものが2つあります。スラッシュは、JSONが要求していないのでエスケープしません。ほかのツールの出力では\/を見かけることがありますが、これは正当ではあっても不要な書き方で、HTML内で</script>という並びを隠すための古い習慣に由来します。非ASCII文字も\uエスケープに変換せずそのまま残すので、アクセント付きの文字、日本語などのCJK文字、絵文字も読める状態に保たれます。どちらの形式もパーサーにとっては同じ意味で、読める形式のほうが差分を取りやすくなります。
二重エンコードと、それが厄介な理由
このツールが存在する理由は、エスケープがべき等ではないことにあります。多くの人は、別の問題をデバッグしている最中にこれに気づきます。ドキュメントを1回エスケープすると、引用符は\"になります。その結果をもう一度エスケープすると\\\"になります。直前に追加したバックスラッシュ自体が、エスケープの必要な文字になるからです。回数を重ねるごとに、バックスラッシュはおおよそ倍に増えます。
これは2回シリアライズされたペイロードの特徴です。オブジェクトを保持すべきフィールドにJSONの文字列が入っていて、ログを読むと引用符にバックスラッシュが付いているのが見えます。フレームワークがすでにシリアライズした値に対して、コードがもう一度シリアライザーを呼んだときに起こります。修正方法はほぼ常に、デコード処理を追加することではなく、2つの呼び出しのうち1つを削除することです。
それを解きほぐすのが文字列化の解除です。エンコードの層ごとに1回ずつ実行すると、そのたびにバックスラッシュが半分になるのがわかります。バックスラッシュがなくなり、結果がオブジェクトとして解析できれば、何層重なっていたかがわかります。
エスケープした文字列が必要になる場面
別のJSONドキュメントにペイロードを埋め込むとき。Webhookのエンベロープ、ジョブキュー、監査ログは、任意の本文をたいていこの方法で運びます。環境変数にJSONの値を設定するとき。そうしないとシェルが引用符を取り除いてしまいます。CIジョブのYAMLファイルにリクエスト本文を貼り付けるとき。ソースコード内にテスト用のフィクスチャをリテラルとして書くとき。ネイティブのJSON列ではなくテキスト列にドキュメントを保存するとき。
いずれの場合も、内側の引用符は、そのままでは終端として扱われてしまう解析の層をくぐり抜けなければなりません。それを無傷で運ぶのがエスケープの役割です。
エスケープ規則を1文字ずつ見る
JSONは、文字列の中にそのまま書けない文字を厳密に定めています。二重引用符はそこで文字列が終わってしまうため \" になります。バックスラッシュはエスケープの開始記号なので \\ と2つ重ねます。U+0020より下の制御文字はすべて禁止されており、そのうち5つには短い表記があります。改行は \n、復帰は \r、タブは \t、バックスペースは \b、フォームフィードは \f です。この範囲の残りの文字は \u0000 のような6文字のエスケープになります。
必須ではないのによくエスケープされる文字が2つあります。スラッシュは \/ と書けます。これは規格上正しいものの不要です。この習慣はJSONをHTMLに埋め込む場面から来ています。そのままだと </script> という並びがscript要素を途中で終わらせてしまうからです。非ASCII文字も \u エスケープで書けます。出力を純粋なASCIIに保つため、既定でそうするライブラリもあります。
このツールはどちらもそのまま残します。読みやすい出力のほうが差分を取りやすく、どのパーサーも2つの形式を同一に扱うからです。後続のシステムが本当にASCIIのみの出力を必要とするなら、それはシリアライザ側のフラグの問題であり、ドキュメント自体が求めるものではありません。
シェルを通してJSONを安全に渡す
エスケープが最も失敗しやすいのはシェルです。引用符の層が同時に2つ重なるからです。bashのシングルクォートは中身をすべてそのまま通します。だから curl -d '{"name":"value"}' は動き、同じ文字列をダブルクォートで囲むと動きません。シェルが変数を展開し、文字を取り除いてから curl に渡してしまうためです。
ペイロード自体にシングルクォートが含まれると、シングルクォートで囲む方法も破綻します。確実な答えは、コマンドラインに埋め込むのをやめることです。本文をファイルに書き出して curl -d @body.json を使うか、パイプで流し込みます。生成したペイロードについては、文字列連結より jq -n のほうが安全です。作りからして妥当なJSONになるためです。
JSONを入れた環境変数も同種の問題で、特にCIの設定やDockerで起こります。その値はたいていYAMLのクォート、シェルの展開、アプリケーション自身のパースという3つの層を生き延びる必要があります。正気を保つ方法は、JSONをbase64でエンコードして渡し、使う場所でデコードすることです。
構造化ログと二重エンコードの痕跡
ログ基盤では1行に1つのJSONオブジェクトを期待することが増えています。こうすればフィールドを検索でき、自由書式のテキストに正規表現をかけずに済みます。落とし穴は、すでにシリアライズ済みのオブジェクトをログに出すことです。そのフィールドはネストしたオブジェクトではなくJSONの文字列として届き、ログ基盤はそれを不透明なひと塊としてインデックスします。
目に見える兆候は、まさにこのツールが解きほぐすためにある痕跡です。値が {"id":42} ではなく {\"id\":42} のように見えます。層が1つ増えるごとにバックスラッシュはおおよそ倍になるので、数を数えれば何回エンコードされたかが分かります。
直すべき場所は出力側です。自分でシリアライズして文字列を渡すのではなく、オブジェクトをそのままロガーに渡し、シリアライズは1回だけ任せます。文字列が他所から届いていて変更できない場合は、多くのログ基盤に取り込み時にフィールドを展開できるJSONパースのフィルターがあります。クエリのたびに解くよりそちらのほうが良い方法です。
別のドキュメントにペイロードを埋め込む
Webhookのエンベロープ、ジョブキュー、監査ログはどれも同じ問題に直面します。独自のフィールドを持つラッパーが、任意の本文を運ばなければならないという問題です。その本文を文字列にシリアライズするのが単純な答えで、代償として内側のドキュメントが不透明になります。もう一度パースしない限り、クエリも検証もインデックスもできません。
もう一方の選択肢、本文をネストしたオブジェクトのまま持つ方法はクエリ可能性を保ちますが、ラッパーのスキーマが任意の構造を許す必要があり、ドキュメントを走査するものから見ると2つの名前空間が混ざります。多くのシステムは用途ごとに使い分けに落ち着きます。本文を調べるならネスト、ただ中継するだけなら文字列化です。
ペイロードが大きい場合やバイナリの場合に覚えておきたい第3の選択肢があります。中身は別の場所に置き、参照だけを送るのです。エンベロープにURLとチェックサムを入れれば、エスケープもサイズ上限も避けられます。大きなファイルを扱うワークフローは、結局そうなることがほとんどです。
ツールなしでエスケープされたテキストを読む
短い値なら目でエンコードを解くのは十分可能で、練習する価値もあります。何が起きたのかが分かるからです。層が1つならキーと値の周りに \" が見えます。2つなら \\\" が見え、文字列全体もそれ自体がエスケープされた引用符で囲まれているのが普通です。それ以上になるとバックスラッシュが中身より多くなり、読むより数えたほうが早くなります。
役に立つ手がかりは文字列の先頭です。正しくネストしたオブジェクトはエスケープされていない波括弧で始まります。二重にエンコードされたものは引用符に続いてエスケープされた波括弧が来ます。この形は一目で分かるようになります。
ターミナルでは printf '%s\n' "$value" が1層を解き、jq -r '.' がもう1層を解きます。順に適用してバックスラッシュが毎回半分になるのを眺めるのが、ここでunstringifyボタンがやっていることのコマンドライン版です。
よくある質問
- 文字列化では何が行われますか?
- JSONを検証して最小化したあと、全体を引用符で囲み、内部の引用符や制御文字をエスケープします。ソースコードにそのまま埋め込める文字列リテラルが得られます。
- エスケープ済みJSON文字列はどんなときに必要ですか?
- 別のJSON、シェルコマンド、環境変数、テストデータなどにJSONを埋め込む場合です。そのままでは内部の引用符が文字列を途中で終わらせてしまいます。
- 「引用符で囲まれた文字列ではない」と表示されるのはなぜですか?
- 復元機能は全体が二重引用符で囲まれた文字列リテラルを想定しています。オブジェクトをそのまま貼り付けた場合は「文字列化」を使用してください。
- データはサーバーに送信されますか?
- いいえ。エスケープ処理はすべてブラウザ内のJavaScriptで実行されるため、データが端末外に出ることはありません。
最終更新:
操作手順、期待した結果、実際の結果を記載してください。秘密情報や個人情報は削除してください。
