テキストとしての入れ子データ
JSON は、設定ファイルや、プログラムや Web API 間でやり取りするデータの日常的な形式です。オブジェクト {"key": value}、配列 [1, 2]、文字列、数値、true/false、null でできており、Python にそのまま対応します。オブジェクトは辞書、配列はリスト、true/false は True/False、null は None になります。CSV の表と違って入れ子にでき、注文が約定のリストを持ち、それぞれが独自の項目を持つこともできます。
import json
# Text to Python: loads() ("load string")
text = '{"symbol": "AAPL", "fills": [{"price": 184.25, "qty": 100}], "open": true, "note": null}'
order = json.loads(text)
print(order["fills"][0]["price"]) # 184.25
print(order["open"], order["note"]) # True None
# Python to text: dumps() ("dump string")
print(json.dumps({"symbol": "AAPL", "qty": 200})) # {"symbol": "AAPL", "qty": 200}ファイル:load と dump
s のない版はファイルを扱います。json.load(file) は開いたファイルを読み込んで解析し、json.dump(data, file) は書き出します。indent=2 で人が読みやすくなり、sort_keys=True でキーの順序が安定するため、バージョン管理での差分が小さくなります。常に with と encoding="utf-8" で開きましょう。
import json
# Save a config, then load it back
config = {"fee": 0.001, "symbols": ["MSFT", "AAPL"], "limits": {"max_qty": 500}}
with open("config.json", "w", encoding="utf-8") as file:
json.dump(config, file, indent=2, sort_keys=True)
with open("config.json", encoding="utf-8") as file:
print(file.read())
# {
# "fee": 0.001,
# "limits": {
# "max_qty": 500
# },
# "symbols": [
# "MSFT",
# "AAPL"
# ]
# }
with open("config.json", encoding="utf-8") as file:
print(json.load(file) == config) # True往復で失われるもの
JSON の型は少ないため、Python の値の一部は途中で変わります。タプルはリストに戻り、辞書のキーは必ず文字列に戻り({1: "a"} は {"1": "a"} になる)、集合・日付・独自オブジェクトは変換するまで書き出せません(TypeError)。日付は .isoformat()、集合は sorted(...) で変換します。NaN と無限大も正式な JSON ではないことを覚えておきましょう。
import json
from datetime import date
# Tuples become lists, int keys become strings
data = {"pair": (1, 2), 7: "seven"}
print(json.loads(json.dumps(data))) # {'pair': [1, 2], '7': 'seven'}
# A date has to be converted by hand
today = date(2025, 1, 2)
print(json.dumps({"day": today.isoformat()})) # {"day": "2025-01-02"}不正な入力
取引所からのメッセージのように外部から届くテキストは、そもそも有効な JSON ではないかもしれません。その場合 json.loads は ValueError の一種である json.JSONDecodeError を送出し、有効な JSON でも必要なキーがない(KeyError)、形が違う(TypeError)ことがあります。解析を try/except で囲み、不正なメッセージがプログラムにとって何を意味するかを決めましょう。次のトピックで例外を詳しく学びます。
import json
# Bad input raises, so catch it where you parse
def get_price(text):
try:
return json.loads(text)["price"]
except (ValueError, KeyError, TypeError):
return None
print(get_price('{"price": 184.25}')) # 184.25
print(get_price("not json")) # None
print(get_price('{"symbol": "AAPL"}')) # Nonejson.dumps や json.dump、読み込みは json.loads や json.load に任せましょう。設定の統合も同じで、変更する前にコピーして、ある設定が別の設定に混ざらないようにします。json.loads(text)/json.dumps(data)は文字列、json.load(file)/json.dump(data, file)はファイル用indent=2とsort_keys=Trueで、ファイルが読みやすく安定する- 不正な入力は
ValueError、KeyError、TypeErrorを送出する。想定するものだけ捕まえる
次は例外とエラー処理です。プログラムが分かりやすく失敗し、必要なときに回復できるようにします。
ラボ:採点される内容
order_totalで JSON ファイル内の注文を合計する25点save_configで設定を読みやすい JSON として保存する25点parse_quoteで気配値メッセージを安全に読み取る25点merge_configsで既定値に設定を重ねる25点