FastAPI请求体详解:用Pydantic定义复杂数据结构

1. 什么是请求体?

在Web开发中,我们经常需要处理客户端发送的数据。FastAPI中,当客户端通过POST、PUT等方法发送复杂数据(比如JSON格式)时,这些数据会被放在请求体(Request Body)中。和通过URL传递的查询参数(Query Parameters)不同,请求体更适合传递结构化数据(如用户信息、表单等)。

举个例子:用户注册时,我们需要收集姓名、年龄、地址等信息,这些数据如果用查询参数会显得冗长且不安全,因此通常放在请求体中。

2. Pydantic是什么?

Pydantic是FastAPI推荐使用的数据验证和解析库。它允许我们定义数据结构(如用户信息、商品信息),并自动进行数据类型检查、格式验证,还能将请求体数据转换为Python对象。

简单来说,Pydantic就像一个“数据保镖”,确保你收到的数据符合预期格式,避免因数据错误导致的API异常。

3. 定义第一个Pydantic模型

首先,我们需要安装Pydantic(FastAPI已内置,若未安装可通过pip install pydantic安装)。然后,通过继承pydantic.BaseModel来定义数据模型。

示例1:简单模型(无嵌套)

from pydantic import BaseModel

# 定义一个User模型,包含name和age字段
class User(BaseModel):
    name: str  # 字符串类型,必填
    age: int   # 整数类型,必填

# FastAPI接口:接收User模型作为请求体
from fastapi import FastAPI

app = FastAPI()

@app.post("/users/")
def create_user(user: User):
    # FastAPI会自动解析请求体为User对象
    return {"message": f"用户 {user.name} 创建成功!年龄:{user.age}"}

测试这个接口
用Postman或curl发送POST请求到http://localhost:8000/users/,请求体为JSON:

{
    "name": "小明",
    "age": 20
}

接口会返回成功信息:{"message": "用户 小明 创建成功!年龄:20"}

4. 复杂数据结构:嵌套模型

当数据包含子结构时(比如用户有地址信息),我们可以在模型中嵌套其他Pydantic模型。

示例2:嵌套模型(用户+地址)

from pydantic import BaseModel
from typing import Optional  # 用于可选字段

# 先定义子模型:地址
class Address(BaseModel):
    street: str  # 街道
    city: str    # 城市
    zipcode: str = "100000"  # 可选,默认值

# 定义主模型:用户(嵌套Address)
class User(BaseModel):
    name: str
    age: int
    address: Optional[Address] = None  # 地址是可选的(Optional+默认None)

@app.post("/users/with-address/")
def create_user_with_address(user: User):
    # 访问嵌套字段:user.address.street
    return {
        "name": user.name,
        "address": user.address.dict() if user.address else "无地址"
    }

测试
请求体中加入地址信息:

{
    "name": "小红",
    "age": 22,
    "address": {
        "street": "科技路100号",
        "city": "北京"
    }
}

接口返回:{"name": "小红", "address": {"street": "科技路100号", "city": "北京", "zipcode": "100000"}}

5. 复杂数据结构:列表类型

如果需要接收多个同类数据(如用户的兴趣爱好列表),可以用List类型(需导入typing.List)。

示例3:列表类型(兴趣爱好列表)

from pydantic import BaseModel
from typing import List, Optional

class User(BaseModel):
    name: str
    age: int
    hobbies: List[str] = []  # 字符串列表,默认空列表
    extra_info: Optional[dict] = None  # 可选字典(比如额外信息)

@app.post("/users/with-hobbies/")
def create_user_with_hobbies(user: User):
    return {
        "name": user.name,
        "hobbies": user.hobbies,
        "extra_info": user.extra_info if user.extra_info else "无额外信息"
    }

测试
请求体包含列表:

{
    "name": "小刚",
    "age": 25,
    "hobbies": ["篮球", "编程"],
    "extra_info": {"height": 180, "weight": 70}
}

接口返回:{"name": "小刚", "hobbies": ["篮球", "编程"], "extra_info": {"height": 180, "weight": 70}}

6. 复杂数据结构:嵌套列表

如果列表中包含其他模型(比如多个商品),可以用List[模型名]

示例4:嵌套列表(商品列表)

from pydantic import BaseModel

class Item(BaseModel):
    name: str
    price: float

class Order(BaseModel):
    order_id: int
    items: List[Item]  # 列表中的每个元素是Item模型

@app.post("/orders/")
def create_order(order: Order):
    total = sum(item.price for item in order.items)
    return {
        "order_id": order.order_id,
        "items": [item.dict() for item in order.items],
        "total_price": total
    }

测试
请求体包含嵌套列表:

{
    "order_id": 1001,
    "items": [
        {"name": "苹果", "price": 5.0},
        {"name": "香蕉", "price": 3.0}
    ]
}

接口返回总价格:{"order_id": 1001, "items": [{"name": "苹果", "price": 5.0}, {"name": "香蕉", "price": 3.0}], "total_price": 8.0}

7. 数据验证:自动拦截错误数据

Pydantic会自动检查数据是否符合模型定义。如果数据类型错误(比如age写成字符串),FastAPI会返回422 Validation Error

示例5:验证失败测试
发送错误请求体(age是字符串):

{
    "name": "小丽",
    "age": "20",  # 错误类型:字符串而非整数
    "hobbies": ["画画"]
}

接口会返回错误:

{
    "detail": [
        {
            "loc": ["body", "age"],
            "msg": "输入值不是整数",
            "type": "type_error.integer"
        }
    ]
}

8. 总结

  • 请求体:适合传递复杂数据(如JSON),通过POST/PUT方法发送。
  • Pydantic:定义数据结构、自动验证、减少手动解析代码。
  • 嵌套模型:用模型名作为字段类型实现子结构(如用户+地址)。
  • 列表/字典:支持Listdict类型,满足多数据项需求。
  • 自动验证:不符合模型的请求会被FastAPI自动拦截,返回清晰错误信息。

掌握Pydantic是FastAPI处理复杂请求体的核心技能,能让你的API开发更规范、更健壮。

(注:所有代码可直接在FastAPI项目中运行,需安装fastapiuvicornpip install fastapi uvicorn,运行命令:uvicorn main:app --reload

小夜