Python DataclassのカスタムJSONエンコーダー作成

カスタムJSONEncoderサブクラスとdefault関数を使用して、Python dataclass、enum、datetime、カスタム型をJSONにシリアライズする方法を学びます。

Serialization

詳細な説明

PythonオブジェクトからJSONへのシリアライゼーション

Pythonの json.dumps() はデフォルトではdataclass、enum、datetimeオブジェクトをシリアライズできません。カスタムエンコーダーまたは default 関数が必要です。

問題点

import json
from dataclasses import dataclass
from datetime import datetime

@dataclass
class User:
    id: int
    name: str
    created_at: datetime

user = User(id=1, name="Alice", created_at=datetime.now())
json.dumps(user)  # TypeError: Object of type User is not JSON serializable

解決策1:dataclasses.asdict()

from dataclasses import asdict

json.dumps(asdict(user), default=str)

asdict() はdataclassを再帰的にdictに変換します。default=str はdatetimeを str() で処理します。

解決策2:カスタムエンコーダークラス

from dataclasses import dataclass, asdict
from datetime import datetime, date
from enum import Enum
import json

class CustomEncoder(json.JSONEncoder):
    def default(self, obj):
        if hasattr(obj, "__dataclass_fields__"):
            return asdict(obj)
        if isinstance(obj, datetime):
            return obj.isoformat()
        if isinstance(obj, date):
            return obj.isoformat()
        if isinstance(obj, Enum):
            return obj.value
        if isinstance(obj, set):
            return list(obj)
        return super().default(obj)

json.dumps(user, cls=CustomEncoder)
# '{"id": 1, "name": "Alice", "created_at": "2024-06-15T09:30:00"}'

解決策3:default関数

def json_default(obj):
    if hasattr(obj, "__dataclass_fields__"):
        return asdict(obj)
    if isinstance(obj, (datetime, date)):
        return obj.isoformat()
    if isinstance(obj, Enum):
        return obj.value
    raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable")

json.dumps(user, default=json_default)

Pydanticの場合(組み込み)

Pydanticモデルは組み込みのシリアライゼーションを持っています:

class User(BaseModel):
    id: int
    name: str
    created_at: datetime

user.model_dump_json()
# '{"id":1,"name":"Alice","created_at":"2024-06-15T09:30:00"}'

フィールド名の制御

dict内包表記でsnake_caseをcamelCaseに変換するには:

def to_camel(s: str) -> str:
    parts = s.split("_")
    return parts[0] + "".join(p.capitalize() for p in parts[1:])

data = {to_camel(k): v for k, v in asdict(user).items()}

ラウンドトリップチェックリスト

  1. デシリアライズjson.loads() + dataclass構築またはPydantic model_validate_json()
  2. 処理 — 型付きPythonオブジェクトで作業。
  3. シリアライズ — カスタムエンコーダー、asdict() + json.dumps()、またはPydantic model_dump_json()

ユースケース

FlaskやDjangoでREST APIを構築しており、dataclass、enum、datetimeフィールドを含むレスポンスオブジェクトをHTTPレスポンスボディ用のクリーンなJSONにシリアライズする必要がある場合に使用します。

試してみる — JSON to Python Converter

フルツールを開く