diff --git a/lib/document.js b/lib/document.js index db5347f4efd..ad216686859 100644 --- a/lib/document.js +++ b/lib/document.js @@ -3464,9 +3464,9 @@ Document.prototype.$toObject = function(options, json) { const path = json ? 'toJSON' : 'toObject'; const baseOptions = this.constructor && - this.constructor.base && - this.constructor.base.options && - get(this.constructor.base.options, path) || {}; + this.constructor.base && + this.constructor.base.options && + get(this.constructor.base.options, path) || {}; const schemaOptions = this.$__schema && this.$__schema.options || {}; // merge base default options with Schema's set default options if available. // `clone` is necessary here because `utils.options` directly modifies the second input. @@ -3503,7 +3503,8 @@ Document.prototype.$toObject = function(options, json) { _isNested: true, json: json, minimize: _minimize, - flattenMaps: flattenMaps + flattenMaps: flattenMaps, + _seen: (options && options._seen) || new Map() }); if (utils.hasUserDefinedProperty(options, 'getters')) { diff --git a/lib/helpers/clone.js b/lib/helpers/clone.js index 7e0fd97f9eb..6936fa642fc 100644 --- a/lib/helpers/clone.js +++ b/lib/helpers/clone.js @@ -119,9 +119,15 @@ module.exports = clone; function cloneObject(obj, options, isArrayChild) { const minimize = options && options.minimize; const omitUndefined = options && options.omitUndefined; + const seen = options && options._seen; const ret = {}; let hasKeys; + if (seen && seen.has(obj)) { + return seen.get(obj); + } else if (seen) { + seen.set(obj, ret); + } if (trustedSymbol in obj) { ret[trustedSymbol] = obj[trustedSymbol]; } diff --git a/test/document.test.js b/test/document.test.js index 1f3d35decc3..0950dd4fc6a 100644 --- a/test/document.test.js +++ b/test/document.test.js @@ -1035,6 +1035,25 @@ describe('document', function() { assert.ok(posts[0].postedBy._id); }); + it('handles infinite recursion (gh-11756)', function() { + const User = db.model('User', Schema({ + name: { type: String, required: true }, + posts: [{ type: mongoose.Types.ObjectId, ref: 'Post' }] + })); + + const Post = db.model('Post', Schema({ + creator: { type: Schema.Types.ObjectId, ref: 'User' } + })); + + const user = new User({ name: 'Test', posts: [] }); + const post = new Post({ creator: user }); + user.posts.push(post); + + const inspected = post.inspect(); + assert.ok(inspected); + assert.equal(inspected.creator.posts[0].creator.name, 'Test'); + }); + it('populate on nested path (gh-5703)', function() { const toySchema = new mongoose.Schema({ color: String }); const Toy = db.model('Cat', toySchema);