PythonでJSONを扱うとき、特殊文字のエスケープでつまずくポイントは実はほぼ2つに絞られます。「日本語が\u3053...のように化けて見える」問題と、「ダブルクォートやバックスラッシュのエスケープが多すぎて不安になる」問題です。

結論から言うと、エスケープはjson.dumps()に任せれば自動で正しく処理されます。自分で文字列を置換すると二重エスケープを招きやすいので、基本は手動でやりません。この記事では、まず自動エスケープの仕組みを押さえたうえで、上の2つのよくある悩みを解決します。
前提: json.dumps() が特殊文字を自動でエスケープする
JSONの文字列はダブルクォートで囲まれ、仕様上いくつかの文字はエスケープが必須です。対象は主に次の3種類です。
- ダブルクォート
" - バックスラッシュ
\ - 制御文字(改行やタブなど U+0000〜U+001F)
json.dumps()を使えば、これらは意識しなくても自動でエスケープされます。
import json
data = {
"quote": 'He said, "Hello"',
"backslash": "path\\to\\file",
"newline": "line1\nline2",
}
print(json.dumps(data))出力は次のように、必要な文字だけが正しくエスケープされます。
{"quote": "He said, \"Hello\"", "backslash": "path\\to\\file", "newline": "line1\nline2"}よくある悩み1: 日本語が \uXXXX になる → ensure_ascii=False
json.dumps()はデフォルトでensure_ascii=Trueのため、日本語などの非ASCII文字を\uXXXX形式にエスケープします。これが「化けて見える」正体です(データとしては正しい)。
data = {"name": "東京", "message": "こんにちは"}
print(json.dumps(data))
# {"name": "\u6771\u4eac", "message": "\u3053\u3093\u306b\u3061\u306f"}日本語をそのまま出力したい場合はensure_ascii=Falseを指定します。
print(json.dumps(data, ensure_ascii=False))
# {"name": "東京", "message": "こんにちは"}使い分けの目安は次の通りです。
ensure_ascii=True(既定): ASCIIだけで安全に転送したいとき。どんな環境でも文字化けしないensure_ascii=False: ログやファイルを人が読むとき、日本語をそのまま見せたいとき。UTF-8での出力とセットにする
よくある悩み2: バックスラッシュやクォートが多すぎて見える
\\\\や\"が多くて不安になるのは、多くの場合表示上の見え方の問題です。print()で出したJSON文字列と、repr()やデバッガでの見え方は異なります。正しくエスケープされているかは、json.loads()で読み戻して元に戻るか確認するのが確実です。
import json
s = json.dumps({"path": "C:\\Users\\me"})
print(s) # {"path": "C:\\Users\\me"}
print(json.loads(s)) # {'path': 'C:\\Users\\me'} 元に戻る読み戻して元のデータに一致すれば、エスケープは正しく行われています。
ファイルへの読み書き
ファイルに書き出すときもjson.dump()が同様にエスケープします。日本語をそのまま保存したいなら、ensure_ascii=Falseとencoding="utf-8"を両方指定します。
data = {"name": "東京", "message": "こんにちは"}
with open("data.json", "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False)
with open("data.json", "r", encoding="utf-8") as f:
loaded = json.load(f)
print(loaded)手動エスケープは基本不要(必要なケースと注意)
「JSONの一部を文字列連結で組み立てたい」といった理由で手動エスケープを考えることがありますが、基本は避けてください。エスケープ漏れや二重エスケープでバグの温床になります。値をエスケープしたいだけなら、その値だけをjson.dumps()に通すのが安全です。
value = 'He said, "Hello"\\end'
# NG: 手動置換は漏れ・二重エスケープを招きやすい
# escaped = value.replace('"', '\\"')
# OK: 値だけ json.dumps に通す(前後のダブルクォート込みで返る)
escaped = json.dumps(value)
print(escaped) # "He said, \"Hello\"\\end"まとめ
- 特殊文字(
"・\・制御文字)のエスケープはjson.dumps()が自動で行う - 日本語が
\uXXXXになるのは既定のensure_ascii=Trueのため。そのまま出すならensure_ascii=False - バックスラッシュが多く見えても表示上の問題が多い。
json.loads()で読み戻して検証する - ファイル出力は
ensure_ascii=False+encoding="utf-8" - 手動エスケープは避け、値だけ
json.dumps()に通す