JSON 转 Pydantic 模型实战:字段类型、校验与嵌套结构
把一段 JSON 粘进去就拿到带类型注解和校验的 Pydantic BaseModel,本文讲清字段类型推断、Optional、嵌套子模型、FastAPI 请求体,以及它和 dataclass 的真正区别。
JSON 转 Pydantic 模型实战:字段类型、校验与嵌套结构
接第三方 API 时最烦的一步,往往不是发请求,而是给响应手写数据模型。一个 user 对象三十多个字段,你照着 JSON 一个一个敲 id: int、name: str、created_at: str,敲错一个字段名,运行时才在 KeyError 里炸出来。把这一步交给工具:粘一段 JSON,拿一组带类型注解和校验的 Pydantic 模型,直接拖进 .py 文件。
Pydantic BaseModel 自带类型校验,这是它和 dataclass 的分水岭
很多人第一反应是 @dataclass。但 dataclass 的类型注解只是给编辑器看的提示,运行时一个字符串塞进 int 字段它照单全收,不报错。Pydantic 的 BaseModel 不一样:类型注解就是校验规则。model_validate(data) 在构造对象那一刻就按声明的类型解析和校验,类型对不上直接抛 ValidationError,而不是把脏数据放进系统等三层调用之后才出事。
这就是为什么接外部数据时我更倾向 Pydantic。dataclass 适合内部、你完全可控的数据;一旦数据来自 API、webhook 或配置文件,你需要的是一道在边界上就拦住坏数据的关卡,而不是一个只是长得像类型安全的容器。
一段 JSON,出来一组模型
类型是推断出来的,不是一锅端成 Any。整数样本是 int,带小数的是 float,true / false 是 bool,字符串是 str。来看一个真实例子,输入这段 JSON:
{
"id": 1,
"name": "Ada",
"score": 4.5,
"active": true,
"address": { "city": "SF", "zip": "94105" }
}
生成的 Pydantic v2 模型(根名设为 User):
from pydantic import BaseModel
class Address(BaseModel):
city: str
zip: str
class User(BaseModel):
id: int
name: str
score: float
active: bool
address: Address
注意 address 没有被压成一个 dict,而是抽成独立的 Address 子模型,并且排在父模型之前输出,所以文件从上到下读、没有任何前向引用。
嵌套模型:有多深跟多深
嵌套对象会按 key 的 PascalCase 命名抽成各自的子 BaseModel。service 里有 tls、根里有 service,就各自成为一个类,层层对应。对象数组也会折叠成一个共享模型:如果列表第二个元素比第一个多带一个 key,出来是一个把那个 key 标成 Optional 的模型,而不是两个几乎重复、要你手动对齐的类。
Optional 的触发有两个信号。一是值出现过 null,{"company": null} 会得到 company: Optional[Any] = None;二是 key 在对象数组的部分元素里有、部分没有。单个对象没有「缺失」信号,所以所有字段都判为必需。想探索可选性,把一个样本包成 [ ... ] 再粘进去。这部分类型推断的细节,也可以先用 /zh/t/json-formatter/ 把杂乱的 payload 格式化、确认结构后再转。
FastAPI 请求体:校验和文档白送
这套模型在 FastAPI 里价值最大。同事发来 /orders POST 的一个 JSON body 样例,粘进来命名 CreateOrderRequest,选 Pydantic v2,把它接到路由签名上:
@app.post("/orders")
def create_order(body: CreateOrderRequest) -> CreateOrderResponse:
...
FastAPI 会自动用这个模型校验进来的 body,字段缺失或类型不对直接返回 422,OpenAPI 文档也一并生成。响应样例同样粘进来命名 CreateOrderResponse。两个模型放进 schemas.py,在你写 handler 之前,校验和接口文档就已经到位。
不是合法 Python 标识符的 key(比如 html-url)会清洗成 snake_case,原始 key 用 Field(alias=...) 保留,线上数据照样能正确映射。v1 和 v2 的字段注解几乎一样,区别只在别名配置:v2 写 model_config = ConfigDict(populate_by_name=True),v1 写嵌套的 class Config。选和你 requirements 里 pydantic 对上的版本即可。
省去手写这件事值多少
直接试一下:/zh/t/json-to-pydantic/。全部在浏览器里跑,JSON 不碰服务器。如果你的目标不是校验而是轻量数据容器,看 /zh/t/json-to-python-dataclass/;前端要对齐同一份数据结构,用 /zh/t/json-to-typescript-interface/ 或 /zh/t/typescript-to-zod-schema/ 把类型一路接到运行时校验。手写三十个字段要十分钟还容易抄错,粘一次只要几秒,省下的时间留给真正该想的业务逻辑。
Made by Toolora · Updated 2026-06-13