Public

PythonでJSON文字列の特殊文字を正しくエスケープする方法

※本記事には広告・アフィリエイトリンクが含まれます

JavaScript/Node.js/TypeScript Python
t-salad t-salad committed 5d4ff1b

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

スポンサーリンク
Python JSON エスケープ

結論から言うと、エスケープは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=Falseencoding="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=Falseencoding="utf-8"
  • 手動エスケープは避け、値だけjson.dumps()に通す

参考

スポンサーリンク
スポンサーリンク
★ この記事をXでシェア はてなブックマーク
関連記事