Mongoose post save钩子中如何获取保存后的文档值?
问题场景
我在使用Mongoose的pre和post钩子时遇到了一个困扰:针对Config Schema,我希望在save操作完成后触发post钩子,把最终的文档内容写入config.json文件。
最开始我写了这样的代码:
configSchema.post('save', function(result) { const config = {}; Object.entries(this).forEach(([key, item]) => { if(key.charAt(0) !== '_') config[key] = item; console.log(`config[${key}] = ${config[key]}`); }); const newConfig = JSON.stringify(config); const JSON_FILE = path.join(__dirname, '../config.json'); return fs.writeFile(JSON_FILE, newConfig, 'utf8', (err) => { if (err) console.log(err); }); });
但运行后发现,不管是this还是参数result,都没办法拿到我想要的最终文档内容,这让我摸不着头脑,不知道该从哪里获取正确的文档数据。另外我还想搞清楚,在pre钩子中能不能获取到save操作的结果?
解决方案:用toJSON()转换文档实例
后来我找到了可行的解决方法——使用Mongoose文档实例的toJSON()方法,修改后的代码如下:
configSchema.post('save', function(result) { const config = {}; const oldConfig = this.toJSON(); Object.entries(oldConfig).forEach(([key, item]) => { console.log(`key.charAt(0) === ${key.charAt(0)}`); if(key.charAt(0) !== '_') config[key] = item; }); const newConfig = JSON.stringify(config); const JSON_FILE = path.join(__dirname, '../config.json'); return fs.writeFile(JSON_FILE, newConfig, 'utf8', (err) => { if (err) console.log(err); }); });
为什么toJSON()能解决问题?
Mongoose的文档实例并不是普通的JavaScript对象,它内部藏着很多Mongoose自带的属性(比如_id、__v,还有各种内部方法和元数据),直接用Object.entries(this)遍历的话,会把这些内部属性也混进来。而toJSON()方法会把文档实例转换成纯净的JavaScript对象,自动过滤掉那些Mongoose的内部字段,这样我们就能拿到只包含业务属性的对象了。
关于pre钩子获取save结果的说明
这里要明确一个关键点:pre('save')钩子是在保存操作执行之前触发的,这时候文档还没有被写入数据库,所以根本不存在“save操作的结果”。
如果要在pre钩子中操作文档,直接用this就可以——它指向当前待保存的文档实例,你可以修改它的属性,这些修改会被同步到后续的save操作中。举个简单的例子:
configSchema.pre('save', function(next) { // 在保存前自动更新updatedAt字段 this.updatedAt = new Date(); // 必须调用next()才能继续执行后续的钩子和save流程 next(); });
如果是想获取只有保存后才会生成的值(比如新建文档时MongoDB自动生成的_id),那在pre钩子中是拿不到的,这种情况只能在post('save')钩子中获取,因为只有保存完成后,这些值才会被赋值到文档实例上。
内容的提问来源于stack exchange,提问作者Mikita Melnikau

