如何在Mongoose中为子文档添加HATEOAS链接(禁用lean())
解决Mongoose子文档添加HATEOAS链接的问题
问题的核心在于:你通过owner.dogs.id(...)获取到的是Mongoose子文档对象,而非普通JavaScript对象。Mongoose会严格按照Schema定义的字段序列化JSON,直接添加未在Schema中定义的links属性不会被包含在响应里。同时你确实不能对Owner.findById使用.lean(),因为普通JS数组没有内置的.id()方法来查找子文档。
下面提供两种可行的解决方案:
方法1:将子文档转为普通JS对象后添加属性
这是最直接的方案,利用Mongoose文档的toObject()方法将子文档转为普通对象,之后就可以自由添加自定义属性了:
router.get('/owners/:ownerID/dogs/:dogID', (req, res, next) => { Owner.findById(req.params.ownerID, (err, owner) => { if (err) return next(err) const dog = owner.dogs.id(req.params.dogID) // 转换为普通JS对象 const dogResponse = dog.toObject() // 添加HATEOAS链接 dogResponse.links = [ { "rel": 'self', "method": 'GET', "href": `/owners/${owner.id}/dogs/${dog.id}` }, { "rel": 'create', "method": 'POST', "href": `/owners/${owner.id}/dogs/` }, { "rel": 'edit', "method": 'PUT', "href": `/owners/${owner.id}/dogs/${dog.id}` }, { "rel": 'delete', "method": 'DELETE', "href": `/owners/${owner.id}/dogs/${dog.id}` } ] res.json(dogResponse) }) })
方法2:使用Mongoose虚拟属性(Virtuals)
如果需要在多个路由中为Dog子文档添加HATEOAS链接,可以定义虚拟属性,让Mongoose自动在JSON序列化时包含该属性:
首先修改你的Dog Schema,添加虚拟属性并配置JSON序列化选项:
let dogSchema = new Schema({ "name": { type: String, required: true }, "ownerID": { type: String, required: true }, "breed": { type: String, required: true }, "gender": { type: String, required: true }, "weight": { type: Number, required: false }, "length": { type: Number, required: false }, "img_src": { type: String, required: true } }) // 定义links虚拟属性 dogSchema.virtual('links').get(function() { // 通过this.parent()获取父文档(Owner) const owner = this.parent() return [ { "rel": 'self', "method": 'GET', "href": `/owners/${owner._id}/dogs/${this._id}` }, { "rel": 'create', "method": 'POST', "href": `/owners/${owner._id}/dogs/` }, { "rel": 'edit', "method": 'PUT', "href": `/owners/${owner._id}/dogs/${this._id}` }, { "rel": 'delete', "method": 'DELETE', "href": `/owners/${owner._id}/dogs/${this._id}` } ] }) // 配置toJSON选项,确保虚拟属性被包含在序列化结果中 dogSchema.set('toJSON', { virtuals: true })
之后你的路由代码可以保持简洁,直接返回子文档即可:
router.get('/owners/:ownerID/dogs/:dogID', (req, res, next) => { Owner.findById(req.params.ownerID, (err, owner) => { if (err) return next(err) const dog = owner.dogs.id(req.params.dogID) res.json(dog) // 序列化时会自动包含links属性 }) })
两种方案各有优势:方法1适合单个路由的临时需求,方法2适合需要复用链接逻辑的场景。
内容的提问来源于stack exchange,提问作者csk87
相关产品推荐
相关产品推荐

