JSON 转 Python 类工具

从 JSON 生成带类型注解的 Python 类,并附带用于解析和序列化的 from_dict 与 to_dict 方法。

从 JSON 生成带类型注解的 Python 类

json.loads 返回的是层层嵌套的 dict,键名拼错只有在运行时才会暴露,编辑器也无法提供自动补全。本工具会为 JSON 中的每个对象生成一个类,属性带有类型注解(str、int、float、bool、List[...]),并提供一个在解析时检查每个值类型的 from_dict 静态方法,以及一个将对象转换回 dict 以便输出 JSON 的 to_dict 方法。代码只用到标准库中的 typing 模块。

使用方法

  1. 粘贴一段 JSON 样例,例如 webhook 推送内容或 API 响应。
  2. 为根类命名,例如 Order。
  3. 将代码保存为模块,使用 Order.from_dict(json.loads(text)) 解析数据;需要回传时,先调用 to_dict() 再交给 json.dumps。

常见问题

生成的是 dataclass 还是 Pydantic 模型?
都不是。它们是带类型注解和显式 from_dict/to_dict 方法的普通类,不需要任何第三方包。如果项目中已经在用 dataclass 或 Pydantic,可以自行加上 @dataclass,或把字段迁移到 Pydantic 模型中。
如果数据与类型不匹配会怎样?
生成的辅助函数使用 assert isinstance 进行检查,因此当某个值的类型不符合预期时,from_dict 会抛出 AssertionError。注意,使用 -O 参数运行 Python 时 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 转换为其他语言