PythonのJSONで日本語が文字化け?ensure_asciiとUTF-8を分けて確かめる

プログラミング・Web開発カテゴリを表すパンダのイラスト プログラミング・Web開発

この記事について
この記事は、生成AIを活用した自動生成フローで作成しています。Python公式のjsonモジュール仕様と文字コードの説明をもとに、架空の備品データで比較する手順を整理しました。

検証ステータス:📘 公式仕様確認済み・掲載コードの実行未確認

PythonのJSONで日本語が文字化け?ensure_asciiとUTF-8を分けて確かめる

Pythonで日本語をJSONに変換したとき、「マウス」が読めない英数字の並びに変わって驚くことがあります。しかし、その表示だけで文字化けと判断するのは早計です。JSONの文字列を表す方法と、ファイルに記録するバイト列の文字コードは、別の仕組みだからです。両者を混同すると、正常なファイルを修正してかえって読み取れなくしたり、本当の文字コード不一致を見落としたりします。

日本語が見えないのに、値は壊れていないことがある

備品管理の小さなデータを別システムに渡す場面を考えます。担当者は品名と数量をPythonの辞書にまとめ、JSONにして保存しました。テキストエディターで開くと日本語がバックスラッシュと英数字の組合せに見えます。ここで確かめるべきことは二つあります。JSONを読み戻した値が元と一致するか。そしてファイルのバイト列が、受け取り側の想定する文字コードで保存されているかです。

前者が一致するなら、少なくともこの往復では情報を取り戻せています。後者が一致しなければ、画面では日本語に見えても別環境で失敗する可能性があります。見た目、意味、バイト列という三つの層を分けると、原因を追いやすくなります。

手元だけで試す安全な実験

対象はPython 3の標準ライブラリです。外部サービスへの通信も追加パッケージも不要です。以下は架空の品名だけを使い、ファイルは一時ディレクトリに作成します。業務データや個人情報を入力する必要はありません。実行するときは、新しい作業環境でコードを保存し、Python 3で実行してください。

import json
from pathlib import Path
from tempfile import TemporaryDirectory

item = {"品名": "マウス", "数量": 2}
escaped = json.dumps(item, ensure_ascii=True)
readable = json.dumps(item, ensure_ascii=False)

print("ASCII表記:", escaped)
print("日本語表記:", readable)
print("戻した値は同じ:", json.loads(escaped) == json.loads(readable) == item)

with TemporaryDirectory() as folder:
    path = Path(folder) / "items.json"
    path.write_text(readable, encoding="utf-8")
    raw = path.read_bytes()
    restored = json.loads(path.read_text(encoding="utf-8"))
    print("UTF-8バイト数:", len(raw))
    print("保存後も一致:", restored == item)

最初の二行の表示形式は異なりますが、どちらも同じ辞書に復元されることが期待されます。ASCII表記では日本語の文字がUnicodeエスケープで表され、日本語表記ではその文字を直接含むJSON文字列になります。三行目の比較は、見た目の違いがデータの違いを意味しないことを確かめるためのものです。ここに示したのは公式仕様から予想される結果であり、掲載コードの実行結果を実測したものではありません。

一時ディレクトリを使う理由は、既存の業務ファイルを誤って上書きしないためです。処理が終わればテスト用ファイルは削除されます。ファイル名が同じでも本番の保存先へ置き換える前に、保存先の権限と既存ファイルの扱いを別途確認してください。

何がどの順番で変わるのか

次の図は、Pythonの辞書からJSON文字列を作り、さらにファイルへ保存して読み戻すまでの境界を示します。重要なのは、JSONの表記を選ぶ段階と、文字列をバイト列に変える段階が別々に存在することです。

flowchart TD
    A[Pythonの辞書] --> B[json.dumps]
    B --> C{ensure_ascii}
    C -->|True| D[Unicodeエスケープ中心の文字列]
    C -->|False| E[日本語を直接含む文字列]
    D --> F[UTF-8でファイル保存]
    E --> F
    F --> G[UTF-8で読み込み]
    G --> H[json.loadsで辞書に戻す]

図の分岐は文字列の表記方法を選ぶだけです。どちらの経路でも、ファイル保存時にUTF-8を指定できます。逆に、読み込み側が異なる文字コードを前提にすれば、どちらの設定でも問題が起こり得ます。ここが「ensure_asciiをFalseにすれば文字化けが直る」という説明の危険なところです。

Python公式のjsonモジュールでは、ensure_asciiがTrueの場合、ASCII以外の文字をエスケープして出力すると説明されています。Falseでは、必須のエスケープ対象を除き文字をそのまま出力します。どちらもPythonの文字列を返し、ファイルのエンコーディングそのものを設定する引数ではありません。

変える条件は一つだけにする

まずensure_asciiだけをTrueとFalseに変え、json.loadsで元の辞書に戻るか比較してください。この段階では保存時の文字コードを変えません。これにより、JSONの表記方法の差だけを観察できます。次に、両方をUTF-8でバイト列に変換した場合の長さを比較すると、見た目が異なる表現はファイルサイズも変わり得ることが分かります。

import json

for flag in (True, False):
    text = json.dumps({"品名": "マウス"}, ensure_ascii=flag)
    data = text.encode("utf-8")
    print(flag, len(text), len(data), json.loads(data.decode("utf-8")))

len(text)はPython文字列の文字数で、len(data)はUTF-8に変換した後のバイト数です。日本語はUTF-8で複数バイトになるため、文字数とバイト数は通常一致しません。一方、Unicodeエスケープはバックスラッシュや英数字を使うので、表記は長くなることがあります。どちらが短いかを一般論で決めつけず、実際のデータで比較するのが確実です。

この違いは、APIの送信サイズ制限やログの容量見積りにも関係します。ただしJSONを圧縮して送る場合は、単純な文字数の比較が転送サイズに直結するわけではありません。目的が人間の読みやすさならFalse、ASCIIだけの表記が必要な連携ならTrueを検討できますが、どちらも受信側の仕様に合わせて決めます。

文字化けとエスケープを切り分ける

画面にバックスラッシュとuから始まる並びが見える場合、まずjson.loadsで復元できるか確かめます。復元できるなら、JSONの表記として正常な可能性があります。一方、置換文字の「�」が見えたり、読み込み時にUnicodeDecodeErrorが出たりするなら、バイト列と文字コードの対応を調べます。エスケープの有無を切り替えても、すでに誤ってデコードされた文字列は元に戻りません。

JSONDecodeErrorは文字コードとは別に、JSONとしての構文が不正なときにも発生します。たとえばキーを二重引用符で囲んでいない、末尾に余分なカンマがある、途中でファイルが切れているといった場合です。例外名だけで原因を断定せず、どの段階で失敗したかを確認してください。ファイルの読み込みで失敗したのか、読み込めた文字列をJSONとして解析するときに失敗したのかで、調べる場所が変わります。

保存時にencodingを明示したら、読み戻し側でも同じ指定を使います。OSの既定文字コードに依存すると、開発PCでは成功して別のサーバーでは失敗する原因になります。ファイルを他社システムへ渡す場合は、仕様書でUTF-8かどうか、BOMの扱い、改行形式、JSONのトップレベルの形を確認します。特にBOMは受信実装によって扱いが異なるため、勝手に付加しないことが重要です。

業務で使う場合の判断

設定ファイルを人が直接読むなら、ensure_ascii=Falseのほうが日本語の値を確認しやすいことがあります。差分レビューでも品名がそのまま見えるため、変更点を追いやすくなります。ただしターミナルの表示文字コードが合っていなければ、ファイル自体は正常でも表示が崩れることがあります。表示の不具合と保存データの破損を分けて考えてください。

API連携では相手側の仕様を優先します。HTTPで送る場合、Content-Typeに文字コードの条件があるか、サーバー側がどの形式を受け付けるかを確認します。JSONのensure_asciiは通信ヘッダーを設定するものではありません。ログ用途では、読みやすさだけでなく秘密情報の混入を防ぐことも必要です。サンプルの品名を実際の顧客名や認証情報に置き換えたログを無制限に保存しないでください。

大量データを読み込む場合、外部から受け取ったJSONを無制限に処理しないことも大切です。Python公式文書は、不正または過大な入力がCPUやメモリを大量消費し得る点を注意しています。ファイルサイズの上限、受信元、タイムアウト、例外時の記録範囲を運用側で設計してください。

最後に、テストでは「表示された文字列が日本語か」だけを合格条件にしないことをおすすめします。元の辞書と読み戻した辞書の一致、実際のバイト列のデコード、連携先の仕様への適合という三段階で確かめれば、正常なUnicodeエスケープを文字化けと誤診せずに済みます。

公式情報・一次情報

文書情報

記事タイトル
PythonのJSONで日本語が文字化け?ensure_asciiとUTF-8を分けて確かめる
作成日
更新日
Source URL
https://papanda925.com/?p=18571

ライセンス: 本記事のうち、当サイトが権利を有する本文・自作図表は、特記なき限り CC BY 4.0 で利用できます。生成AIを活用して作成・編集した内容を含みます。コードについて、別途ライセンス表示またはリンク先GitHubリポジトリのライセンスがある場合は、その条件を優先します。引用・第三者資料・画像・商標等は本ライセンスの対象外です。 利用ポリシー

タイトルとURLをコピーしました