Node.js MongoDB驱动使用BSON而非JSON,EJSON.stringify()可安全序列化BSON类型,而生产环境推荐调用toJSON()方法转换后再JSON.stringify()。

Node.js MongoDB驱动本身不直接“解析JSON”,它处理的是BSON——MongoDB原生的二进制序列化格式。所谓“无法解析JSON”,通常是开发者误把BSON文档当普通JSON字符串处理,或在日志、调试、API响应中手动调用JSON.stringify()时遇到TypeError: Do not know how to serialize a BSON object这类错误。
为什么JSON.stringify()会报错:BSON类型不可序列化
从collection.find().toArray()拿到的文档里含有ObjectId、Date、Decimal128等BSON类型,它们不是纯JS原生值。JSON.stringify()不认识这些类型,直接抛错。
- 常见错误现象:
TypeError: Do not know how to serialize a BSON ObjectId - 典型触发场景:把查询结果直接传给
res.json()(Express)、写入日志、或前端AJAX返回前未转换 - 根本原因:BSON类型需显式转为可JSON化的表示,不能依赖默认序列化
用EJSON.stringify()替代JSON.stringify()
mongodb包内置了EJSON(Extended JSON)工具,专为BSON↔JSON双向转换设计,能安全保留类型语义。
- 安装无额外依赖:v4.0+ 驱动已自带,无需
npm install bson单独引入 - 正确用法:
import { EJSON } from 'mongodb'; const doc = { _id: new ObjectId('60a1b2c3d4e5f67890123456'), createdAt: new Date() }; console.log(EJSON.stringify(doc)); // {"_id": {"$oid": "60a1b2c3d4e5f67890123456"}, "createdAt": {"$date": "2021-05-15T10:30:00.000Z"}} - 注意:
EJSON.stringify()输出的是扩展JSON格式(含$oid、$date等键),浏览器JSON.parse()无法直接识别——仅适合日志、调试、或后端间传输;若要前端消费,仍需进一步清洗
生产环境推荐:用toJSON()方法做轻量转换
对大多数API响应场景,不需要EJSON的完整语义,只需把BSON类型转成合理字符串/数字即可。各BSON类都实现了toJSON()方法,配合JSON.stringify()更简洁:
-
ObjectId.prototype.toJSON()→ 返回十六进制字符串 -
Date.prototype.toJSON()→ 返回ISO字符串(与JSON.stringify()默认行为一致) -
Decimal128.prototype.toJSON()→ 返回数值字符串(如"123.45") - 实操建议:
const docs = await collection.find().toArray(); // 直接利用各BSON实例的toJSON,无需EJSON res.json(docs); // Express内部会调用JSON.stringify,自动触发toJSON
- 陷阱:若手动
JSON.stringify(docs)仍报错,说明你用了旧版驱动(toJSON——检查mongodb版本是否≥4.0
自定义序列化函数:应对特殊字段或兼容性要求
当需要统一控制所有BSON类型的输出格式(例如把ObjectId全转成id字段、Date转为秒级时间戳),可写一个递归serializeBSON()函数:
- 关键点:只处理明确知道的BSON类型,避免误改普通对象
- 示例逻辑:
function serializeBSON(obj) { if (obj == null) return obj; if (obj instanceof ObjectId) return obj.toHexString(); if (obj instanceof Date) return Math.floor(obj.getTime() / 1000); if (obj instanceof Decimal128) return obj.toString(); if (Array.isArray(obj)) return obj.map(serializeBSON); if (typeof obj === 'object') { const result = {}; for (const [k, v] of Object.entries(obj)) { result[k] = serializeBSON(v); } return result; } return obj; } - 性能提示:该函数是同步递归,大数据量时注意栈深度;生产API建议用流式或分页控制数据体积
真正容易被忽略的是:EJSON不是为前端服务的,它的$oid语法在浏览器里无法被JSON.parse()识别。如果你在API里返回EJSON字符串,前端必须配套用EJSON.parse()——这反而增加耦合。日常开发中,优先走toJSON()路径,只在调试、日志、跨系统数据交换时才用EJSON。











