FastAPI 中读取任意有效的 JSON 请求体
问题描述
在 FastAPI 开发中,我们经常需要接收 JSON 格式的请求体。但有时需求不仅仅是处理特定结构的对象或数组,还需要支持任意有效的 JSON 数据,包括:
- 基本数据类型:数字、字符串、布尔值、
null
- 复杂数据结构:对象、数组
- 混合类型结构
使用 Pydantic 模型虽然能处理结构化数据,但对于完全任意的 JSON 数据就显得局限。那么如何在不牺牲 FastAPI 验证功能的情况下,灵活处理任意 JSON 请求体呢?
最佳解决方案
方法一:使用 Any
类型与 Body
注解
这是官方推荐的方法,利用了 FastAPI 的内置功能:
python
from typing import Any
from fastapi import FastAPI, Body
app = FastAPI()
@app.post('/test')
async def process_json(payload: Any = Body(...)):
return payload
关键说明
Any
类型允许接收任何有效的 JSON 数据类型Body(...)
中的...
表示该参数是必需的- FastAPI 会自动验证输入是否为有效 JSON,无效时会返回标准验证错误
- 这种方法支持所有 JSON 数据类型:
null
、true
、false
、字符串、数字、对象和数组
方法二:使用 Annotated
注解(Python 3.9+)
对于更新版本的 Python,可以使用更现代的语法:
python
from typing import Any
from fastapi import FastAPI, Body
from typing_extensions import Annotated # 对于 Python < 3.9
app = FastAPI()
@app.post("/test")
async def get_body(payload: Annotated[Any, Body()]):
return payload
其他可行方案
方案三:使用 Request 对象直接获取
python
from fastapi import Request, FastAPI
app = FastAPI()
@app.post("/raw-json")
async def get_body(request: Request):
return await request.json()
注意事项
这种方法虽然灵活,但:
- 需要手动处理异常
- 失去了自动的请求验证和文档生成功能
- 建议仅在需要完全控制请求处理时使用
方案四:使用 Dict 类型接收对象
python
from typing import Dict, Any
from fastapi import FastAPI
app = FastAPI()
@app.post("/dict")
async def post_dict(data: Dict[str, Any]):
return data
局限性
这种方法只能接收 JSON 对象,无法处理简单值(如数字、字符串)或数组
验证测试示例
确保你的解决方案正确处理各种 JSON 数据类型:
python
import pytest
from fastapi.testclient import TestClient
from your_app import app # 导入你的 FastAPI 应用
client = TestClient(app)
test_data = [
('{"key": 1}', {"key": 1}, "简单对象"),
('{"key": null}', {"key": None}, "包含 null 的对象"),
('[1, 2, 3]', [1, 2, 3], "数字数组"),
('"hello"', "hello", "纯字符串"),
('42', 42, "纯数字"),
('true', True, "布尔值 true"),
('false', False, "布尔值 false"),
('null', None, "纯 null"),
('[]', [], "空数组"),
('{}', {}, "空对象")
]
@pytest.mark.parametrize("payload,expected,test_name", test_data)
def test_json_variations(payload, expected, test_name):
response = client.post("/test", content=payload)
assert response.status_code == 200
assert response.json() == expected
实际应用场景
场景一:通用 Webhook 处理器
python
from typing import Any
from fastapi import FastAPI, Body, HTTPException
app = FastAPI()
@app.post("/webhook/{service_name}")
async def webhook_handler(service_name: str, payload: Any = Body(...)):
try:
# 根据不同的服务处理不同的 JSON 结构
if service_name == "github":
return process_github_webhook(payload)
elif service_name == "stripe":
return process_stripe_webhook(payload)
else:
return {"status": "received", "data": payload}
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))
场景二:JSON 数据存储服务
python
from typing import Any
from fastapi import FastAPI, Body
import json
from datetime import datetime
app = FastAPI()
@app.post("/store-json")
async def store_json_document(
document: Any = Body(...),
collection: str = "default"
):
# 生成文档ID和时间戳
doc_id = generate_id()
timestamp = datetime.utcnow()
# 存储任意JSON数据
store_to_database({
"id": doc_id,
"collection": collection,
"data": document,
"created_at": timestamp
})
return {"id": doc_id, "status": "stored"}
常见问题解答
Q: 如何处理非 JSON 请求体?
A: FastAPI 会自动验证请求内容类型。如果收到非 JSON 数据,会返回 422 错误,包含详细的验证信息。
Q: 如何设置可选的 JSON 请求体?
A: 将 Body(...)
改为 Body(None)
:
python
async def optional_json(payload: Any = Body(None)):
if payload is None:
return {"message": "No data received"}
return payload
Q: 这种方法会影响 API 文档吗?
A: 不会。FastAPI 会自动生成正确的 OpenAPI 文档,将请求体显示为接受任意 JSON。
总结
在 FastAPI 中接收任意有效的 JSON 数据有多种方法:
- 推荐方法:使用
Any
类型与Body
注解(自动验证 + 文档生成) - 备用方案:使用
Request
对象(完全控制但需手动处理) - 限制方案:使用
Dict[str, Any]
(仅限对象类型)
根据你的具体需求选择合适的方法。对于大多数情况,方法一是最佳选择,因为它保持了 FastAPI 的所有优势:自动验证、序列化和文档生成,同时提供最大的灵活性。