json.dumps() 报错因 ObjectId 非 JSON 原生类型,需用 bson.json_util.dumps() 或自定义 JSONEncoder 处理,后者需在 default() 中特判 ObjectId 并返回 str(obj),同时注意 datetime 等其他 BSON 类型。

为什么 json.dumps() 会报 TypeError: Object of type ObjectId is not JSON serializable
Python 3.11 本身不改变 JSON 序列化逻辑,但你升级后更可能暴露这个老问题:MongoDB 驱动(如 pymongo)查出的文档里含 ObjectId 字段,而标准 json.dumps() 完全不认识它。这不是 Python 版本 bug,是类型缺失映射。
常见触发场景:用 FastAPI 返回 MongoDB 查询结果、Flask jsonify()、或直接 print(json.dumps(doc)) 调试时崩掉。
-
ObjectId是 PyMongo 自定义类,没实现__json__()或自动转字符串的协议 - Python 3.11 的
json模块反而更严格,某些旧版“碰巧能过”的非标准序列化路径被堵死了 - 别试图用
str(doc['_id'])手动转——文档嵌套深时容易漏字段,且破坏原始结构
用 json_util.dumps() 替代原生 json.dumps()
PyMongo 自带的 json_util 就是干这事的:它把 ObjectId、datetime、Binary 等 BSON 类型转成 JSON 友好格式(比如 ObjectId 变成 {"$oid": "..."}),还能反向还原。
注意它返回的是字符串,不是 dict;若需后续操作,得用 json_util.loads() 解析回来(但通常你只是要输出)。
立即学习“Python免费学习笔记(深入)”;
- 导入方式:
from bson.json_util import dumps(不是import json) - 直接传整个文档或文档列表:
dumps(doc)或dumps(list(cursor)) - 支持
indent、sort_keys等参数,和原生json.dumps()用法一致 - FastAPI 中可配合
Response(content=..., media_type="application/json")直接返回
自定义 JSONEncoder 更适合 Web 框架集成
如果你用 Flask、Django REST Framework 或需要保持返回 dict 类型(比如给前端传 raw data),继承 json.JSONEncoder 更可控。
关键点:只重写 default() 方法,对 ObjectId 返回 str(),其他类型交给父类处理——别覆盖整个编码流程。
- 示例编码器:
class MongoJSONEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, ObjectId): return str(obj) return super().default(obj) - 使用:
json.dumps(doc, cls=MongoJSONEncoder) - Flask 中可设全局:
app.json_encoder = MongoJSONEncoder(Flask ≥ 2.2) - ⚠️ 警惕:别在
default()里无条件调str(obj),会把int、list全转成字符串
生产环境别忘了检查 datetime 和嵌套 ObjectId
ObjectId 错误常是第一个报错,但真正上线后,datetime 字段、嵌套文档里的 _id、甚至 Decimal128 同样会卡住。用 json_util 最省心,它一并处理。
如果坚持手写 Encoder,务必测试这些场景:
- 文档顶层
_id和子文档里的author.id -
created_at: datetime.utcnow()字段 - 数组里混着
ObjectId和字符串:["abc", ObjectId(...)] - 空值:
None、NaN(json_util默认转成null,手写要额外判断)
最不容易翻车的做法:查完数据立刻用 json_util.dumps() 包一层,别留到框架自动序列化环节。


















