如何为Mongoose对象数组类型的Schema添加虚拟字段?
问题描述
我现有如下Mongoose Schema:
const mongoose = require("mongoose"); const PostSchema = new mongoose.Schema( { title:{ type:String, required: true, trim: true }, author:{ type: mongoose.Schema.Types.ObjectId, ref:'Customer' }, images: [ {img :{type:String}} ] }, { timestamps: true } ); PostSchema.virtual("images.imgCrop").get(function () { return `${this.img}/crop/720/720`; }); module.exports = mongoose.model("Post", PostSchema);
当前查询返回的数据示例:
[ { "_id": "64cb808d95a1ec6708a8e5e6", "title": "First Post", "images": [], "createdAt": "2023-08-03T10:33:31.253Z", "updatedAt": "2023-08-03T10:33:31.253Z", "__v": 0 }, { "_id": "64cb829a95a1ec6708a8f9ab", "title": "Next Post", "images": [ { "_id": "64cb829a95a1ec6708a8f9ac", "img": "image001.png" }, { "_id": "64cb829a95a1ec6708a8f9ad", "img": "image002.png" }, { "_id": "64cb829a95a1ec6708a8f9ae", "img": "image003.png" } ], "createdAt": "2023-08-03T10:34:02.173Z", "updatedAt": "2023-08-03T10:34:02.173Z", "__v": 0 } ]
数据库中已有大量数据,我需要在查询返回的images数组每个对象中添加imgCrop字段,值为img路径/crop/720/720。当前查询语句为let posts = await Post.find({}).lean().populate('author'),请问能否通过Mongoose虚拟字段实现该需求?
解决方案
可以通过Mongoose虚拟字段实现,但你当前的虚拟字段写法存在问题,需要调整Schema定义,同时注意lean()方法对虚拟字段的影响,以下是具体实现:
1. 修正虚拟字段的定义方式
你之前在PostSchema上定义"images.imgCrop"虚拟字段,是试图给整个images数组添加属性,而非数组内的每个子文档。正确的做法是给images的子文档单独定义Schema,并在子文档Schema上添加虚拟字段:
const mongoose = require("mongoose"); // 定义图片子文档Schema,并添加虚拟字段 const ImageSchema = new mongoose.Schema({ img: { type: String } }, { // 配置转换为JSON/普通对象时包含虚拟字段 toJSON: { virtuals: true }, toObject: { virtuals: true } }); ImageSchema.virtual("imgCrop").get(function () { // this指向子文档实例,可直接访问img字段 return `${this.img}/crop/720/720`; }); // 定义PostSchema,引用ImageSchema const PostSchema = new mongoose.Schema( { title:{ type:String, required: true, trim: true }, author:{ type: mongoose.Schema.Types.ObjectId, ref:'Customer' }, images: [ImageSchema] // 使用单独定义的子文档Schema }, { timestamps: true, toJSON: { virtuals: true }, toObject: { virtuals: true } } ); module.exports = mongoose.model("Post", PostSchema);
2. 处理lean()方法的影响
lean()方法会让Mongoose返回普通JavaScript对象而非Mongoose文档实例,默认不会包含虚拟字段,针对这种情况有两种处理方式:
方式一:移除lean(),使用Mongoose文档实例
如果性能允许(数据量不是极端大),可以去掉lean(),此时查询返回的文档会自动包含虚拟字段:
let posts = await Post.find({}).populate('author');
方式二:保留lean(),结合transform函数手动添加字段
如果必须使用lean()提升性能,可以利用lean()的transform选项,手动遍历images数组添加imgCrop字段:
let posts = await Post.find({}) .lean({ transform: (doc) => { if (doc.images && doc.images.length > 0) { doc.images = doc.images.map(img => ({ ...img, imgCrop: `${img.img}/crop/720/720` })); } return doc; } }) .populate('author');
总结
通过调整子文档Schema并正确定义虚拟字段,完全可以实现需求。如果使用lean(),需要额外通过transform手动注入字段;如果不使用lean(),只需配置Schema的toJSON/toObject选项包含虚拟字段即可。
内容的提问来源于stack exchange,提问作者Ajith

