Python講座 / JSONデータを変換して保存する

JSONはオブジェクト、配列、文字列、数値などを表すテキスト形式です。json.load・dumpはファイル、loads・dumpsは文字列を扱い、Pythonのすべての型やオブジェクトをそのまま保存できるわけではありません。

JSONとPythonの値を対応付ける

JSON Python
object dict
array list
string str
true / false / null True / False / None

JSONのキーと文字列には二重引用符を使います。Python風の一重引用符やコメントを自由に追加できる形式ではありません。タプルが保存と再読込後にリストになることもあり、日時やDecimalには明示的な変換規則が必要です。

Unicodeを含む記録を保存して読み直す

json_demo.pyをUTF-8で保存し、仮想環境でpython json_demo.pyを実行します。原文と同じcaféを非ASCII文字の例として使います。実習フォルダーのexpenses-demo.jsonを置き換えるプログラムです。

import json
from pathlib import Path

data = {"currency": "KRW", "expenses": [{"name": "café", "amount": 8000}]}
path = Path("expenses-demo.json")
path.write_text(json.dumps(data, ensure_ascii=False, indent=2, allow_nan=False), encoding="utf-8")
loaded = json.loads(path.read_text(encoding="utf-8"))
amount = loaded["expenses"][0]["amount"]
if type(amount) is not int or amount < 0:
    raise ValueError("invalid amount")
print(loaded["expenses"][0]["name"])
print(amount)

予想結果です。

café
8000

構文解析とアプリケーションの検証を分ける

JSONとして読めても、必要なキー、配列の構造、項目の型や範囲まで正しいとは限りません。例はtype(amount) is intで、intのサブクラスであるboolを除外します。サブクラスを許可したい場合にはisinstanceが適切なこともあり、契約に合わせて選びます。

PythonのJSON処理は、既定では標準JSONにない一部の数値定数を受け付けます。allow_nan=Falseはシリアライズ時にそれらを拒否します。外部入力を厳密に扱うには、そのような定数や重複キーをどう処理するかも明確にします。

文字の表し方とファイルの文字コードを区別する

ensure_ascii=Falseは非ASCII文字をエスケープせず読める形で残す指定で、ファイルの文字コードを選ぶ指定ではありません。UTF-8は別に指定します。indentは見た目を変え、データの意味を変えません。

不正なJSONはJSONDecodeErrorになります。引用符や末尾コンマを修正し、evalへ切り替えて読み込まないでください。amountを-1やtrueに変えて業務上の検証で拒否されることを確認し、8000へ戻します。この例は既知の一件の形が前提なので、一般的な取り込みでは周囲のキーや配列も検証します。HTTPとJSONでも同じ切り分けを使います。

loadとdumpはファイルオブジェクト、loadsとdumpsは文字列を対象にします。dumpsはJSON文字列を返すだけでファイルは作りません。保存するには書き込むかdumpを使います。JSONの解析成功は、必須キーや金額が業務上有効であることを意味しません。

項目 動作
load(file) file → object
loads(text) str → object
dump(obj, file) object → file
dumps(obj) object → str

関連する公式リファレンス