PythonとJSONは相性抜群のペアです。
FastAPIや
DjangoでREST APIを構築する場合でも、
データパイプラインを処理する場合でも、設定ファイルを読み込む場合でも、JSONを常に扱うことになります。
朗報は、Pythonの標準ライブラリには必要なすべてが
jsonモジュール
に揃っているということです。
pip installは不要です。
実際に使う4つの関数
jsonモジュールには日常作業で使う4つの関数があります:
json.loads(str)— JSON文字列をPythonオブジェクトに解析するjson.dumps(obj)— PythonオブジェクトをJSON文字列に変換するjson.load(file)— ファイルオブジェクトから直接JSONを解析するjson.dump(obj, file)— PythonオブジェクトをJSONとしてファイルに書き込む
loads/dumpsのsはstring(文字列)を表します。
sがない方はファイルオブジェクトを扱います。ルールを知れば簡単に覚えられます。
json.loads() — JSON文字列の解析
import json
json_string = '{"name": "Alice", "age": 30, "active": true, "score": 98.5}'
user = json.loads(json_string)
print(user["name"]) # Alice
print(user["age"]) # 30
print(user["active"]) # True
print(type(user)) # <class 'dict'>型マッピングに注目してください:JSONのtrueはPythonのTrue、
JSONのfalseはPythonのFalse、JSONのnullはPythonのNoneになります。
JSONオブジェクトはPythonのdict、JSON配列はPythonのlistになります。
json.dumps() — JSON文字列へのシリアライズ
import json
user = {
"name": "Bob",
"age": 25,
"roles": ["admin", "editor"],
"active": True,
"extra": None
}
# コンパクト(ネットワーク送信に適している)
compact = json.dumps(user)
print(compact)
# {"name": "Bob", "age": 25, "roles": ["admin", "editor"], "active": true, "extra": null}
# 見やすいフォーマット(ログや人間が読む用途に適している)
pretty = json.dumps(user, indent=2)
print(pretty)
# {
# "name": "Bob",
# "age": 25,
# "roles": [
# "admin",
# "editor"
# ],
# "active": true,
# "extra": null
# }逆の型マッピングに注目してください:PythonのTrue → JSONのtrue、
PythonのNone → JSONのnull。Pythonが自動的に処理します。
ファイルからJSONを読み込む
これはおそらく最も一般的なユースケースです — 起動時に設定ファイルやデータファイルを読み込む場合:
import json
# 1ステップで読み込みと解析
with open("config.json", "r", encoding="utf-8") as f:
config = json.load(f)
print(config["database"]["host"]) # localhost
print(config["database"]["port"]) # 5432JSONファイルを開く際は必ずencoding="utf-8"を指定してください。JSONは
RFC 8259
でUTF-8として規定されており、省略するとWindowsでデフォルトのエンコーディングがcp1252になる場合があり問題が発生します。
JSONをファイルに書き込む
import json
results = {
"timestamp": "2024-01-15T09:30:00Z",
"total": 1523,
"processed": 1521,
"failed": 2,
"errors": [
{"id": 42, "reason": "missing field"},
{"id": 99, "reason": "invalid format"}
]
}
with open("results.json", "w", encoding="utf-8") as f:
json.dump(results, f, indent=2)
print("結果をresults.jsonに保存しました")エラーを適切に処理する
json.loads()は、入力が有効なJSONでない場合に
json.JSONDecodeError
(ValueErrorのサブクラス)を発生させます。制御できないデータを解析する場合は必ず処理してください:
import json
def safe_parse(json_str):
try:
return json.loads(json_str)
except json.JSONDecodeError as e:
print(f"無効なJSON - 行{e.lineno}、列{e.colno}: {e.msg}")
return None
data = safe_parse('{"name": "Alice"}') # 正常に動作
bad = safe_parse('not json at all') # エラーを表示してNoneを返す
also_bad = safe_parse('{"key": }') # 位置情報付きでエラーを表示JSONDecodeErrorは解析が失敗した正確な行と列を提供します。
大きなJSONファイルのデバッグに役立ちます。
dumps()の便利なオプション
import json
data = {
"z_key": 1,
"a_key": 2,
"price": 9.999999999
}
# キーをアルファベット順にソート(再現性のある出力やdiffに最適)
print(json.dumps(data, sort_keys=True, indent=2))
# {
# "a_key": 2,
# "price": 9.999999999,
# "z_key": 1
# }
# 非ASCII文字を保持(デフォルト:\uXXXXにエスケープ)
data2 = {"city": "Münich", "greeting": "こんにちは"}
print(json.dumps(data2, ensure_ascii=False))
# {"city": "Münich", "greeting": "こんにちは"}
# ensure_ascii=True(デフォルト)の場合:
print(json.dumps(data2))
# {"city": "M\u00fcnich", "greeting": "\u3053\u3093\u306b\u3061\u306f"}ensure_ascii=Falseは非ASCII文字を含むJSONファイルを書き込む際に常に追加するオプションです。
エスケープされたバージョンは技術的には有効なJSONですが、テキストエディタで読むのがはるかに困難になります。
カスタムオブジェクトのシリアライズ
デフォルトでは、json.dumps()はカスタムクラスのインスタンスや
datetime
オブジェクトをシリアライズできません。2つの方法があります:
json.JSONEncoder
のサブクラスを作成するか、先にdictに変換するかです:
import json
from datetime import datetime, date
# オプション1: カスタムエンコーダークラス
class AppEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, (datetime, date)):
return obj.isoformat()
return super().default(obj)
data = {"name": "Alice", "created_at": datetime(2024, 1, 15, 9, 30)}
print(json.dumps(data, cls=AppEncoder, indent=2))
# {
# "name": "Alice",
# "created_at": "2024-01-15T09:30:00"
# }
# オプション2: default=パラメータ(一回限りの変換に簡単)
print(json.dumps(data, default=str, indent=2)) # 未知のものをすべてstrに変換実践的なパターン:設定ファイルの読み込み
ほぼすべてのPythonプロジェクトで使う実世界のパターンを紹介します — 適切なデフォルト値を持つJSON設定ファイルを読み込む設定ローダーです:
import json
import os
from pathlib import Path
DEFAULTS = {
"database": {"host": "localhost", "port": 5432},
"debug": False,
"log_level": "INFO"
}
def load_config(path="config.json"):
config = DEFAULTS.copy()
config_path = Path(path)
if config_path.exists():
with open(config_path, "r", encoding="utf-8") as f:
try:
user_config = json.load(f)
# ディープマージ:ユーザー設定がデフォルトを上書き
for key, value in user_config.items():
if isinstance(value, dict) and key in config:
config[key].update(value)
else:
config[key] = value
except json.JSONDecodeError as e:
print(f"警告: config.jsonが無効です ({e.msg})、デフォルト値を使用します")
return config
config = load_config()
print(config["database"]["host"]) # localhost(または上書きされた値)まとめ
Pythonのjsonモジュールは依存関係なしで必要なすべてをカバーします。
重要なルール:文字列にはloads()/dumps()を、ファイルにはload()/dump()を使い、
外部データを解析する際は常にJSONDecodeErrorを処理し、
非ラテン文字を含むデータにはensure_ascii=Falseを追加してください。
JSONデータのデバッグには、JSONフォーマッターと
JSONバリデーターが大幅に時間を節約してくれます。