FastAPI端点返回含datetime的字典序列化报错的优化方案咨询
FastAPI datetime序列化问题及优化方案
问题描述
基于Python 3.11.2搭建FastAPI 0.103.1版本的API时,遇到序列化异常:
- 直接返回包含
datetime对象的字典字面量时,前端能正常接收格式正确的日期数据; - 将相同结构的字典赋值给变量后返回,后端会抛出
TypeError: Object of type datetime is not JSON serializable错误。
目前采用return json.loads(json.dumps(model_metadata, default=str))临时解决,但写法冗余,需更优方案。
直接返回正常的写法
@router.post("") async def post_endpoint(payload: dict): ... return { 'train_dates': { 'start_date': datetime.datetime(2018, 1, 1, 0, 0), 'end_date': datetime.datetime(2018, 1, 25, 0, 0) }, 'test_dates': { 'start_date': datetime.datetime(2018, 1, 25, 0, 0), 'end_date': datetime.datetime(2018, 2, 1, 0, 0) }, 'forecast_dates': { 'start_date': datetime.datetime(2018, 2, 1, 0, 0), 'end_date': datetime.datetime(2018, 2, 14, 0, 0) } }
返回变量报错的写法
@router.post("") async def post_endpoint(payload: dict): ... model_metadata = { 'train_dates': { 'start_date': datetime.datetime(2018, 1, 1, 0, 0), 'end_date': datetime.datetime(2018, 1, 25, 0, 0) }, 'test_dates': { 'start_date': datetime.datetime(2018, 1, 25, 0, 0), 'end_date': datetime.datetime(2018, 2, 1, 0, 0) }, 'forecast_dates': { 'start_date': datetime.datetime(2018, 2, 1, 0, 0), 'end_date': datetime.datetime(2018, 2, 14, 0, 0) } } return model_metadata
当前冗余解决方法
return json.loads(json.dumps(model_metadata, default=str))
优化方案
方案1:使用Pydantic模型(推荐)
FastAPI底层依赖Pydantic处理数据序列化,定义对应模型后会自动完成datetime对象的序列化,还能提供数据校验功能。
from pydantic import BaseModel import datetime # 定义日期范围嵌套模型 class DateRange(BaseModel): start_date: datetime.datetime end_date: datetime.datetime # 定义元数据模型 class ModelMetadata(BaseModel): train_dates: DateRange test_dates: DateRange forecast_dates: DateRange @router.post("") async def post_endpoint(payload: dict): ... model_metadata = ModelMetadata( train_dates=DateRange( start_date=datetime.datetime(2018, 1, 1, 0, 0), end_date=datetime.datetime(2018, 1, 25, 0, 0) ), test_dates=DateRange( start_date=datetime.datetime(2018, 1, 25, 0, 0), end_date=datetime.datetime(2018, 2, 1, 0, 0) ), forecast_dates=DateRange( start_date=datetime.datetime(2018, 2, 1, 0, 0), end_date=datetime.datetime(2018, 2, 14, 0, 0) ) ) return model_metadata
方案2:自定义全局JSON编码器
在FastAPI实例初始化时指定自定义JSON编码器,统一处理datetime对象的序列化逻辑,无需在每个端点重复处理。
from fastapi import FastAPI import datetime from json import JSONEncoder # 自定义编码器 class CustomJSONEncoder(JSONEncoder): def default(self, obj): if isinstance(obj, datetime.datetime): return obj.isoformat() # 可根据需求调整格式,如str(obj) return super().default(obj) # 初始化FastAPI时传入编码器 app = FastAPI(json_encoder=CustomJSONEncoder) # 路由端点写法不变 @router.post("") async def post_endpoint(payload: dict): ... model_metadata = { 'train_dates': { 'start_date': datetime.datetime(2018, 1, 1, 0, 0), 'end_date': datetime.datetime(2018, 1, 25, 0, 0) }, 'test_dates': { 'start_date': datetime.datetime(2018, 1, 25, 0, 0), 'end_date': datetime.datetime(2018, 2, 1, 0, 0) }, 'forecast_dates': { 'start_date': datetime.datetime(2018, 2, 1, 0, 0), 'end_date': datetime.datetime(2018, 2, 14, 0, 0) } } return model_metadata
方案3:手动转换datetime为字符串(轻量方案)
构建字典时直接将datetime对象转换为符合需求的字符串格式,避免后续序列化报错,适合简单场景。
@router.post("") async def post_endpoint(payload: dict): ... model_metadata = { 'train_dates': { 'start_date': datetime.datetime(2018, 1, 1, 0, 0).isoformat(), 'end_date': datetime.datetime(2018, 1, 25, 0, 0).isoformat() }, 'test_dates': { 'start_date': datetime.datetime(2018, 1, 25, 0, 0).isoformat(), 'end_date': datetime.datetime(2018, 2, 1, 0, 0).isoformat() }, 'forecast_dates': { 'start_date': datetime.datetime(2018, 2, 1, 0, 0).isoformat(), 'end_date': datetime.datetime(2018, 2, 14, 0, 0).isoformat() } } return model_metadata
内容的提问来源于stack exchange,提问作者Ricardo
相关产品推荐
相关产品推荐

