この記事について
この記事は、生成AIを活用した自動生成フローで作成しています。Python公式のjsonモジュールを参照し、日本語のJSON表示とUTF-8保存を架空データで比較します。検証ステータス:📘 公式仕様確認済み・掲載コードの実機実行は未実施
PythonのJSONで日本語が読めない?ensure_asciiとUTF-8保存を比べる
Pythonの辞書をJSONにしたら日本語がバックスラッシュと英数字に変わった。これは文字化けでしょうか。結論は、Unicodeエスケープによる表現の違いであり、データの破損とは限りません。JSON文字列の見た目、復元した値、ファイルの文字コードを分けて確認する必要があります。
架空の備品台帳を使い、同じデータを二通りに表示して比較します。最初はファイルを一切変更しません。次に練習用の新規ファイルだけへ保存し、読み戻しまで確認する手順を紹介します。実行結果は公式仕様に基づく期待値であり、掲載コードの実機試験は未実施です。
対象環境とゴール
| 項目 | 条件 |
|---|---|
| 対象 | Python 3系、標準ライブラリjson |
| インストール | 追加パッケージ不要 |
| 入力 | 架空の備品名と数量 |
| 最初の操作 | メモリ内でJSONを生成 |
| 保存 | 新規の練習用ファイルだけ |
| 目的 | 表示と保存文字コードの違いを判別 |
JSONはプログラム間で値を交換するテキスト形式です。Pythonの辞書をJSON文字列にするjson.dumpsと、JSON文字列をPythonの値へ戻すjson.loadsを使います。重要なのは、画面で日本語が直接見えない場合でも、読み戻せば元の日本語が得られる可能性があることです。
まず試す:日本語がエスケープされる
import json
data = {"品名": "青いノート", "数量": 3}
a = json.dumps(data)
b = json.dumps(data, ensure_ascii=False)
print("既定:", a)
print("変更:", b)
print("復元一致:", json.loads(a) == json.loads(b))
公式仕様に基づく期待結果では、既定の出力は日本語を\uから始まる表現に変換し、ensure_ascii=Falseでは日本語を直接含む文字列になります。最後の復元一致はTrueとなる想定です。これは実行済みの出力ではありません。
なぜでしょうか。ensure_ascii=Trueは既定値で、非ASCII文字をエスケープする指定です。エスケープは暗号化でも削除でもなく、同じ文字を別の書き方で表すことです。受信側がJSONを正しく解析すれば、元の日本語を復元できます。画面の見た目だけで文字化けと決めつけると、原因を取り違えます。
ただしensure_ascii=Falseでも、JSONの引用符や改行など、文法上必要なエスケープまで消えるわけではありません。「日本語を直接表示する」と「すべての特殊文字を無加工にする」は別です。文字列の扱いを正確に理解するため、この点を最初に押さえてください。
データの意味と文字列の差を分ける
次の図は同じ辞書から二種類のJSON表現を作る流れです。図の分岐は表示形式だけを扱い、ファイル保存や通信は含みません。
flowchart TD
A["日本語を含むPython辞書"] --> B{"ensure_ascii"}
B -->|"True"| C["UnicodeエスケープのJSON"]
B -->|"False"| D["日本語を直接含むJSON"]
C --> E["json.loads"]
D --> E
E --> F["復元後の値を比較"]
図の両側からjson.loadsへ戻る点が重要です。JSON文字列としては異なっていても、解析したデータの意味は一致し得ます。たとえばログの文字列比較では変更ありと出ても、データの比較では変更なしとなる場合があります。
実際に条件を一つだけ変えて確かめます。元の辞書をそのまま使い、エスケープ指定だけを変更します。文字列同士の比較と、復元した値同士の比較を分けます。
import json
data = {"部署": "開発", "件数": 2}
a = json.dumps(data)
b = json.dumps(data, ensure_ascii=False)
print("文字列一致:", a == b)
print("データ一致:", json.loads(a) == json.loads(b))
期待値は文字列一致がFalse、データ一致がTrueです。日本語のエスケープ指定だけで文字列は変わりますが、辞書へ戻した値は同じだからです。この違いは、バックアップの差分や構成管理にも関係します。意味が同じなら変更なしと判断したい場合は解析後の値を比べ、署名やハッシュのようにバイト単位の一致が必要な場合は文字列の表現を固定します。
UTF-8で保存するのは別の処理
ensure_ascii=FalseはJSON文字列の生成方法であり、保存ファイルの文字コードを指定するものではありません。ファイルへの書き込みは、Pythonの文字列をバイト列へ変換する別の段階です。日本語を直接含むJSONを保存する場合、読み書き双方でencoding="utf-8"を明示します。
import json
from pathlib import Path
path = Path("json_unicode_demo_2026.json")
if path.exists():
raise FileExistsError(path)
data = {"品名": "青いノート", "数量": 3}
with path.open("x", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
with path.open("r", encoding="utf-8") as f:
restored = json.load(f)
print("読み戻し一致:", restored == data)
このコードは練習用ファイルを新規作成します。既存ファイルがある場合は失敗するxモードを使い、意図しない上書きを防ぎます。実行する場合は作業フォルダーを確認し、同名ファイルがない場所を選んでください。業務設定ファイルや共有フォルダーで試す必要はありません。
期待結果は読み戻し一致: Trueです。indent=2は読みやすい字下げを付けるだけで、文字コードの指定ではありません。ファイルをテキストエディターで開いて日本語が見えるかを確認し、次にjson.loadで元の辞書へ戻るかを確認します。見た目の検査と値の検査を分けることがポイントです。
失敗時の切り分け
エディターで日本語が崩れている場合、まずエディターがUTF-8として開いているか確認します。Python側がUTF-8で保存しても、別のアプリが異なる文字コードとして解釈すれば表示が崩れる可能性があります。一方、読み戻した辞書が一致するなら、少なくともそのPython環境での保存と復元は成立しています。外部サービスの互換性まで保証するわけではありません。
json.loadsがJSONDecodeErrorを出す場合は、文字コードだけでなくJSONの文法も確認します。余分なカンマ、閉じ忘れた引用符、JSONオブジェクトの単純な連結などは、ensure_asciiを変更しても解決しません。まず入力が一つの正しいJSON文書かを切り分けます。
数値にも境界があります。PythonのJSONエンコーダーは既定でNaNや無限大を出力できますが、厳密なJSONを要求する受信先には不適合となる可能性があります。allow_nan=Falseで非有限値をエラーにする選択肢があります。日本語の問題を直しても、数値の互換性が別の障害として残ることがあるのです。
辞書のキーにも注意が必要です。JSONのオブジェクトのキーは文字列なので、Pythonの数値キーを含む辞書をJSON化して戻すと、元の辞書と一致しないことがあります。今回の例で復元一致が期待できるのは、最初から文字列のキーだけを使っているためです。すべてのPythonオブジェクトが完全に往復できるとは考えないでください。
外部から受け取った大きなJSONを無制限に読み込むことも避けます。Python公式文書は、信頼できない入力が大量のCPUやメモリを使う可能性を警告しています。業務処理では入力サイズの制限や例外処理、受信元の確認を別途設計します。ここで示したコードは学習用であり、本番APIの受信処理そのものではありません。
業務での使い分け
人が読む設定ファイルや調査用ログなら、ensure_ascii=FalseとUTF-8指定は読みやすさにつながります。部署名や品名をそのまま追えるためです。ただし実データには個人情報や認証情報が含まれることがあります。読みやすいからといって機密データをログへ丸ごと出力してはいけません。今回のように架空データで動作を確かめ、必要な項目だけを記録する設計にします。
他システムへ送るJSONでは、相手の仕様を先に確認します。JSONの構造、文字コード、数値の扱い、通信時のヘッダーなどを揃え、受信側で復元した値まで確かめます。送信側の画面に日本語が表示されたことだけを成功条件にすると、実際の不整合を見逃します。
ファイルのハッシュや電子署名を比較する運用では、エスケープ設定や字下げの変更でもバイト列が変わる点に注意します。JSONとして同じ意味でもファイルが同一とは限りません。保存形式を変更する場合は、生成側と検証側の設定を合わせ、変更前後の比較方法を決めてから進めます。
今回の発見は、見た目のエスケープ、JSONの意味、保存バイト列は三つの別の確認対象だということです。最初にメモリ内で二種類の表現を比べ、次に復元後の値を確認し、最後にUTF-8保存と受信側の互換性を調べる。この順番なら、単なる表示差を文字化けと誤診せずに済みます。
