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 模块。
使用方法
- 粘贴一段 JSON 样例,例如 webhook 推送内容或 API 响应。
- 为根类命名,例如 Order。
- 将代码保存为模块,使用 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)