JSON → Python クラス変換ツール

JSONから型付きのPythonクラスを生成します。パースとシリアライズ用のfrom_dict・to_dictメソッド付きです。

JSONから型付きのPythonクラスを生成

json.loadsで得られるのはネストしたdictなので、キーのタイプミスは実行するまで気づけず、エディタの補完も効きません。このツールはJSON内のオブジェクトごとにクラスを生成します。各クラスには型注釈付きの属性(str、int、float、bool、List[...])、パース時に各値の型をチェックするfrom_dict静的メソッド、JSON用にdictへ戻すto_dictメソッドが含まれます。使用するのは標準ライブラリのtypingモジュールだけです。

使い方

  1. WebhookのペイロードやAPIレスポンスなどのJSONサンプルを貼り付けます。
  2. ルートクラスの名前を指定します(例: Order)。
  3. コードをモジュールとして保存し、Order.from_dict(json.loads(text)) でデータをパースします。送信するときは、json.dumpsの前にto_dict()を呼び出します。

よくある質問

dataclassやPydanticのモデルですか?
どちらでもありません。型ヒントと明示的なfrom_dict/to_dictメソッドを持つ通常のクラスなので、サードパーティのパッケージは不要です。プロジェクトですでに使っている場合は、@dataclassを付けたり、フィールドをPydanticに移植したりできます。
データが型と一致しない場合はどうなりますか?
生成されるヘルパー関数はassert isinstanceによるチェックを行うため、想定外の型の値があるとfrom_dictがAssertionErrorを送出します。ただし、Pythonを-Oオプション付きで実行するとassertはスキップされる点に注意してください。
必要なPythonのバージョンは?
typingモジュールの型注釈を使用しており、Python 3.7以降で動作します。

例: JSONからPythonへの変換

入力JSON

{
  "order_id": "A-1001",
  "total": 59.9,
  "paid": true,
  "items": [{ "sku": "BOOK-1", "quantity": 2 }]
}

生成されたPython(Order)

from typing import Any, List, TypeVar, Callable, Type, cast


T = TypeVar("T")


def from_str(x: Any) -> str:
    assert isinstance(x, str)
    return x


def from_int(x: Any) -> int:
    assert isinstance(x, int) and not isinstance(x, bool)
    return x


def from_float(x: Any) -> float:
    assert isinstance(x, (float, int)) and not isinstance(x, bool)
    return float(x)


def from_bool(x: Any) -> bool:
    assert isinstance(x, bool)
    return x


def from_list(f: Callable[[Any], T], x: Any) -> List[T]:
    assert isinstance(x, list)
    return [f(y) for y in x]


def to_float(x: Any) -> float:
    assert isinstance(x, (int, float))
    return x


def to_class(c: Type[T], x: Any) -> dict:
    assert isinstance(x, c)
    return cast(Any, x).to_dict()


class Item:
    sku: str
    quantity: int

    def __init__(self, sku: str, quantity: int) -> None:
        self.sku = sku
        self.quantity = quantity

    @staticmethod
    def from_dict(obj: Any) -> 'Item':
        assert isinstance(obj, dict)
        sku = from_str(obj.get("sku"))
        quantity = from_int(obj.get("quantity"))
        return Item(sku, quantity)

    def to_dict(self) -> dict:
        result: dict = {}
        result["sku"] = from_str(self.sku)
        result["quantity"] = from_int(self.quantity)
        return result


class Order:
    order_id: str
    total: float
    paid: bool
    items: List[Item]

    def __init__(self, order_id: str, total: float, paid: bool, items: List[Item]) -> None:
        self.order_id = order_id
        self.total = total
        self.paid = paid
        self.items = items

    @staticmethod
    def from_dict(obj: Any) -> 'Order':
        assert isinstance(obj, dict)
        order_id = from_str(obj.get("order_id"))
        total = from_float(obj.get("total"))
        paid = from_bool(obj.get("paid"))
        items = from_list(Item.from_dict, obj.get("items"))
        return Order(order_id, total, paid, items)

    def to_dict(self) -> dict:
        result: dict = {}
        result["order_id"] = from_str(self.order_id)
        result["total"] = to_float(self.total)
        result["paid"] = from_bool(self.paid)
        result["items"] = from_list(lambda x: to_class(Item, x), self.items)
        return result


def order_from_dict(s: Any) -> Order:
    return Order.from_dict(s)


def order_to_dict(x: Order) -> Any:
    return to_class(Order, x)

JSONを他の言語に変換