日本語が \u4e2d\u6587 になる理由
要点:\u4e2d\u6587 のエスケープは正当な Unicode で、エンコーディング障害ではありません。本当の文字化けは見た目が違い、両者を見分けることが何を直すべきかを決めます。

要点:\uXXXX の出力は正当なエスケープとして扱い、JSON.parse で往復することを確認し、読める日本語が欲しいときは ensure_ascii=False のような純 ASCII シリアライズを切りましょう。非 ASCII を運べない転送に限ってエスケープを保ちます。
エスケープされたのであって、壊れたのではありません。
JSON.parse('"\\u4e2d\\u6587"') // "中文"
\u4e2d は中、\u6587 は文です。これらは正当なエスケープシーケンスで、どのパーサーも復元します。壊れているものは何もなく、ログ、差分、パケットキャプチャで読めないだけです。
誰がエスケープするか
| ツール | 既定 | 切り替え |
|---|---|---|
Python json.dumps |
エスケープする(ensure_ascii=True) |
ensure_ascii=False |
JavaScript JSON.stringify |
エスケープしない | — |
| Java Jackson | エスケープしない | 任意 |
PHP json_encode |
5.4 以降エスケープしない | — |
Go encoding/json |
エスケープしない | — |
Python が圧倒的に一般的な発生源です。既定には理由があります。純 ASCII 出力はどの転送でも生き残ります。代償はすべての非 ASCII 文字が 6 文字になることです。
エスケープと文字化け
エスケープ:\u4e2d\u6587、\uXXXX に一致。シリアライザ設定が原因。
文字化け:测试、涓枃。UTF-8 バイトを書いて Latin-1 や GBK として読むことが原因。
簡単なテスト:テキストをブラウザコンソールで JSON.parse('"…"') として貼ります。読める形で戻ればエスケープでした。失敗するか文字化けしたままならエンコーディングの不一致で、修正はエンコーディングを揃えることで、シリアライザには触れません。
切り方を知る
json.dumps(data, ensure_ascii=False)
そのうえで下流を検証します。一部のレガシーシステムは本当に ASCII のみの転送に依存しています。特に SMS ゲートウェイや古いメールリレーです。
エスケープが正しい選択であるとき
- 非 ASCII に敵対的な転送(レガシー HTTP ヘッダー、一部の SMS ゲートウェイ)
- 別の JSON 文字列の中にネストされた JSON。エスケープが引用符の衝突を減らします
- バイト単位で切り詰め、マルチバイト文字を分割してしまうログパイプライン
それ以外では可読性を優先し、切ってください。
FAQ
Python が既定で日本語をエスケープするのはなぜですか?
ensure_ascii=True が既定で、どの転送でも生き残る純 ASCII 出力を保証するからです。代償はすべての非 ASCII 文字が 6 文字になることです。
ensure_ascii を切るべきですか?
可読性が重要なときは切ります。そのうえで下流を検証してください。レガシー HTTP ヘッダー、一部の SMS ゲートウェイ、バイト単位で切り詰めるログパイプラインは、ASCII のみの内容に依存しているかもしれません。
転送が非 ASCII を運べない場合はどうなりますか?
エスケープを保ちます。JSON が別の JSON 文字列の中に埋め込まれるとき、または非 ASCII に敵対的なチャネルを通るとき、エスケープは引用符の衝突を避け、マルチバイト文字の分割を防ぎます。