Flask+MongoEngine返回文档前如何统一转换_id与created_at字段格式
Flask + MongoEngine 文档返回字段通用转换方案
不推荐优先使用aggregate聚合实现这类格式转换,聚合返回结果是原生字典而非MongoEngine文档实例,会丢失模型自带的校验、自定义方法等能力,且每个查询都需要重复写投影转换逻辑,维护成本很高。以下是按推荐度排序的可行实现:
方案1:自定义抽象基类统一封装序列化逻辑(最优,全项目通用)
一次定义即可让所有继承基类的模型自动支持统一格式输出,完全保留ORM原生能力,后续格式调整只需要修改基类代码。
- 先定义所有业务模型共用的抽象基类
from mongoengine import Document, DateTimeField, EmbeddedDocument from datetime import datetime class BaseDocument(Document): meta = {'abstract': True} # 标记为抽象类,不会生成独立数据集合 def to_dict(self, time_fmt: str = "%Y-%m-%d %H:%M:%S"): data = self.to_mongo().to_dict() # 统一处理_id转id字符串 if "_id" in data: data["id"] = str(data.pop("_id")) # 统一处理DateTime类型字段转字符串 for field_name, field in self._fields.items(): if field_name not in data: continue # 处理普通文档的时间字段 if isinstance(field, DateTimeField): dt_val: datetime = getattr(self, field_name) # 如需返回毫秒时间戳,替换为 data[field_name] = str(int(dt_val.timestamp()*1000)) data[field_name] = dt_val.strftime(time_fmt) # 递归处理嵌套嵌入式文档 if isinstance(field, EmbeddedDocument): data[field_name] = getattr(self, field_name).to_dict() # 可在这里加全局排除字段逻辑,比如默认不返回敏感字段、文件字段 # data.pop("file", None) return data
- 原有业务模型修改继承关系,字段定义完全不需要改动
class Effects(BaseDocument): name = StringField(unique=True, required=True, min_length=2, max_length=20, validation=name_is_not_taken) parameters = EmbeddedDocumentListField(Parameter) parameterKeys = ListField(StringField(required=True, null=True)) created_at = DateTimeField(default=datetime.now) created_by = StringField(required=True) effect_link = StringField(required=True) description = StringField(required=False) file = FileField(required=False)
- 路由调用时直接使用即可,支持单条、批量查询
# 查询单条 effect = Effects.objects.get(name="gain") return effect.to_dict() # 查询批量 effect_list = Effects.objects.all() return [item.to_dict() for item in effect_list]
如果要实现接口直接返回文档对象自动序列化,可以自定义Flask全局JSON编码器,省去每次手动调用to_dict()的步骤:
import json from flask import Flask from mongoengine.base import BaseDocument as MongoBaseDoc class CustomJSONEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, MongoBaseDoc): return obj.to_dict() return super().default(obj) app = Flask(__name__) app.json_encoder = CustomJSONEncoder
方案2:重写MongoEngine原生JSON序列化规则(轻量场景适用)
如果不想修改模型继承关系,可以直接在调用序列化方法时重写默认的扩展JSON输出规则,覆盖$oid、$date这类特殊结构的输出逻辑:
from bson import ObjectId from datetime import datetime import json def custom_serializer(o): if isinstance(o, ObjectId): return str(o) if isinstance(o, datetime): return o.strftime("%Y-%m-%d %H:%M:%S") raise TypeError(f"Object of type {type(o)} is not JSON serializable") # 使用时直接传入自定义序列化函数 effect = Effects.objects.get(name="gain") return json.loads(json.dumps(effect.to_mongo(), default=custom_serializer))
该方案缺点是每个序列化场景都需要传入自定义规则,无法做到全局统一生效。
补充说明
你给出的期望返回示例中created_at值与id完全一致属于笔误,可根据实际业务需求,在基类的时间字段处理逻辑中调整为格式化时间字符串、ISO标准时间、毫秒级时间戳等任意格式。如果嵌入式文档列表中的子文档也需要做格式转换,只需要在基类的to_dict方法中增加列表类型判断,递归调用子文档的序列化方法即可。
内容的提问来源于stack exchange,提问作者Jurdn
相关产品推荐
相关产品推荐

