JSON文字列化
JSON文字列化は、JSONドキュメントをJSON文字列リテラルへ変換します。これは、あるペイロードを別のペイロードの中に埋め込むときに必要になる、エスケープと引用符を付けた形式です。引用符、バックスラッシュ、制御文字を1回の走査でエスケープするため、検索と置換を続けて行ったときに起こる二重エスケープは発生しません。
JSON文字列化はどう動きますか?
これは、繰り返し行うことになる地味な作業、すなわち「あるペイロードを別のペイロードの中に入れる」という操作の正体です。Webhook定義の中のリクエストボディ。テストファイルの中のフィクスチャ。KubernetesのConfigMapの値としての設定ドキュメント。OpenAPIのexampleフィールドの中のサンプルレスポンス。いずれの場合も、内側のドキュメントは外側の中では文字列にならなければならず、つまり内側のすべての引用符・バックスラッシュ・改行をエスケープする必要があります。
多くの人はこれを検索と置換で手作業に行いますが、失敗の仕方には特定の、予測可能なパターンがあります。先に引用符を置換し、次にバックスラッシュを置換すると、各引用符の直前に置いたばかりのバックスラッシュがもう一度エスケープされます。欲しかったのは\"なのに、できあがるのは\\"で、これは「リテラルのバックスラッシュの後に文字列が終わる」として解釈されます。ドキュメントの中に引用符を含むものが現れるまでは、問題なく動いているように見えます。
1回の文字走査でエスケープすれば、この失敗は起こり得ません。各文字は1度だけ調べられ、1度だけ変換されます。" を \" に、\ を \\ に、改行を \n に、タブを \t に。後のパスが先のパスの出力を再処理する機会は一切ありません。
RFC 8259は、U+0020未満のすべての制御文字をエスケープすることを要求するため、短縮形のない文字は\uシーケンスになります。非ASCII文字はデフォルトではそのまま残ります。現代のJSONはUTF-8であり、エスケープしても結果が大きくなり読みにくくなるだけだからです。ただし、古いログパイプラインや上位バイトを壊すシステムのように、出力先が純粋なASCIIを本当に必要とする場合は、オプションでエスケープできます。その際、Basic Multilingual Planeの外側の文字にはサロゲートペアを正しく出力します。1つの\uでは絵文字を表せず、1つだけ出力すると文字が途切れてしまうためです。
デフォルトでは、入力はまずJSONとして検証されます。壊れた入力をエスケープすると、文字列リテラル自体は有効でも、その中身は後で——たいていずっと厄介な場所で——パースに失敗する文字列になります。ログ行やシェルスニペットなど、任意のテキストをエスケープしたい場合は、この検証をオフにしてください。
ドキュメント
{"name":"Ada","note":"line one
line two"}文字列リテラル
"{\"name\":\"Ada\",\"note\":\"line one\nline two\"}"JSON文字列化が対応するオプションとエッジケースは?
| パラメータ | 型 | デフォルト | 動作とエッジケース |
|---|---|---|---|
| 引用符で囲む | 真偽値 | true | 結果が完全なJSON文字列となるように、周囲の二重引用符を出力します。既存の引用符ペースト内に貼り付ける場合は無効にしてください。 |
| 非ASCII文字をエスケープ | 真偽値 | false | U+007Eを超えるすべてのコードポイントを\uXXXXに変換します。UTF-8ターゲットでは不要であり、出力サイズが増大します。Astral文字は、切り捨てられた単一のエスケープではなく、サロゲートペアとして正しく出力されます。 |
| スラッシュをエスケープ | 真偽値 | false | /を\/として書き込みます。有効なJSONであり、一部のログシッパーやHTML <script>ブロック内に埋め込まれたJSONでは、</script>が要素を早期に終了させないために必要です。 |
| 有効なJSONを必須 | 真偽値 | true | エスケープ前に検証し、リテラルが元のドキュメントにパースできることを保証します。無効にすると、任意のテキストがエスケープされ、ログ行やシェルスニペットに便利です。 |
| 制御文字 | 常にエスケープされる | — | RFC 8259では、文字列内のU+0020未満の生の文字は禁止されています。\n \r \t \b \fはそれぞれショートハンドを使用し、その他はすべて\u00XXになります。 |
| サイズ増加 | ≈ 5–30% | — | 入力に含まれる引用符とバックスラッシュの数に完全に依存します。引用符が大部分を占めるJSONドキュメントは、文章よりも大きくなります。 |
よくある質問
このツールとJavaScriptのJSON.stringifyの違いは何ですか?
JSON.stringifyはJavaScriptの値を受け取り、JSONテキストを生成します。このツールはその逆で、JSONテキストを受け取り、それを含むJSON文字列リテラルを生成します。文字列を渡したときのJSON.stringify(someJsonText)がまさにこれです。この区別は重要です。オブジェクトを文字列化すれば{"a":1}になりますが、テキスト{"a":1}を文字列化すれば"{\"a\":1}"になります。このツールが行うのは後者です。
手作業でエスケープしたJSONが壊れるのはなぜですか?
ほとんどの場合、置換の順序の間違いです。引用符を先に、バックスラッシュを後に置換すると、置換で直前に導入されたバックスラッシュが二重にエスケープされ、\"が\\"になって文字列が早く終わります。ドキュメントに引用符が現れるまでは動作しますが、JSONではすぐに現れます。このツールのように各文字を正確に1度だけ走査すれば、この可能性自体がなくなります。
元に戻すにはどうすればよいですか?
JSONエスケープ解除を使ってください。リテラルを受け取り、元のドキュメントを返します。意図的に寛容に作られており、前後の引用符を失った断片も扱えます。ログ行やスタックトレースからコピーしたものは、たいていその状態だからです。
スラッシュはエスケープすべきですか?
通常は不要です。JSONでは\/が許可されますが、それを要求するパーサーはありません。エスケープが意味を持つのは2つの場合です。HTMLの<script>ブロックへ直接埋め込む場合(文字列内にリテラルの</script>があると、要素がそこで終わってしまう)、そしてエスケープ形式を期待する一部のログシッパーです。
ペイロードはどこかに送信されますか?
いいえ。エスケープはこのブラウザのタブの中で行われます。別のドキュメントに埋め込まれるドキュメントは、たいていフィクスチャ、APIのボディ、設定です。引用符を変えてもらうために、まさにそうしたデータを知らないサーバーに通すべきではありません。
エスケープした文字列をシェルコマンドに貼り付けると壊れるのはなぜですか?
2層のクォートが関わっていて、うちJSONのものは1層だけだからです。このツールが作るのは有効なJSON文字列リテラルですが、貼り付けたものに対してシェルは独自のルールを適用します。JSONが\"と書いたダブルクォートは、bashにとっては普通のダブルクォートです。値全体をシングルクォートで囲むか、ファイルに書き出してそのパスを渡してください。