PythonとJSONは相性抜群のペアです。 FastAPIDjangoで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/dumpssstring(文字列)を表します。 sがない方はファイルオブジェクトを扱います。ルールを知れば簡単に覚えられます。

json.loads() — JSON文字列の解析

python
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文字列へのシリアライズ

python
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を読み込む

これはおそらく最も一般的なユースケースです — 起動時に設定ファイルやデータファイルを読み込む場合:

python
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"])  # 5432

JSONファイルを開く際は必ずencoding="utf-8"を指定してください。JSONは RFC 8259 でUTF-8として規定されており、省略するとWindowsでデフォルトのエンコーディングがcp1252になる場合があり問題が発生します。

JSONをファイルに書き込む

python
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.JSONDecodeErrorValueErrorのサブクラス)を発生させます。制御できないデータを解析する場合は必ず処理してください:

python
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()の便利なオプション

python
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に変換するかです:

python
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設定ファイルを読み込む設定ローダーです:

python
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バリデーターが大幅に時間を節約してくれます。