Skip to content

Commit

Permalink
fix(document): handle casting array of spread docs
Browse files Browse the repository at this point in the history
Fix #11522
  • Loading branch information
vkarpov15 committed Mar 21, 2022
1 parent 67127ff commit 041be89
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 8 deletions.
20 changes: 19 additions & 1 deletion lib/helpers/document/handleSpreadDoc.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,32 @@

const utils = require('../../utils');

const keysToSkip = new Set(['__index', '__parentArray', '_doc']);

/**
* Using spread operator on a Mongoose document gives you a
* POJO that has a tendency to cause infinite recursion. So
* we use this function on `set()` to prevent that.
*/

module.exports = function handleSpreadDoc(v) {
module.exports = function handleSpreadDoc(v, includeExtraKeys) {
if (utils.isPOJO(v) && v.$__ != null && v._doc != null) {
if (includeExtraKeys) {
const extraKeys = {};
for (const key of Object.keys(v)) {
if (typeof key === 'symbol') {
continue;
}
if (key[0] === '$') {
continue;
}
if (keysToSkip.has(key)) {
continue;
}
extraKeys[key] = v[key];
}
return { ...v._doc, ...extraKeys };
}
return v._doc;
}

Expand Down
20 changes: 13 additions & 7 deletions lib/schema/documentarray.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const SchemaDocumentArrayOptions =
const SchemaType = require('../schematype');
const discriminator = require('../helpers/model/discriminator');
const handleIdOption = require('../helpers/schema/handleIdOption');
const handleSpreadDoc = require('../helpers/document/handleSpreadDoc');
const util = require('util');
const utils = require('../utils');
const getConstructor = require('../helpers/discriminator/getConstructor');
Expand Down Expand Up @@ -427,13 +428,18 @@ DocumentArrayPath.prototype.cast = function(value, doc, init, prev, options) {
const Constructor = getConstructor(this.casterConstructor, rawArray[i]);

// Check if the document has a different schema (re gh-3701)
if (rawArray[i].$__ && !(rawArray[i] instanceof Constructor)) {
rawArray[i] = rawArray[i].toObject({
transform: false,
// Special case: if different model, but same schema, apply virtuals
// re: gh-7898
virtuals: rawArray[i].schema === Constructor.schema
});
if (rawArray[i].$__ != null && !(rawArray[i] instanceof Constructor)) {
const spreadDoc = handleSpreadDoc(rawArray[i], true);
if (rawArray[i] !== spreadDoc) {
rawArray[i] = spreadDoc;
} else {
rawArray[i] = rawArray[i].toObject({
transform: false,
// Special case: if different model, but same schema, apply virtuals
// re: gh-7898
virtuals: rawArray[i].schema === Constructor.schema
});
}
}

if (rawArray[i] instanceof Subdocument) {
Expand Down
17 changes: 17 additions & 0 deletions test/document.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11181,4 +11181,21 @@ describe('document', function() {
}
});
});

it('handles casting array of spread documents (gh-11522)', async function() {
const Test = db.model('Test', new Schema({
arr: [{ _id: false, prop1: String, prop2: String }]
}));

const doc = new Test({ arr: [{ prop1: 'test' }] });

doc.arr = doc.arr.map(member => ({
...member,
prop2: 'foo'
}));

assert.deepStrictEqual(doc.toObject().arr, [{ prop1: 'test', prop2: 'foo' }]);

await doc.validate();
});
});

0 comments on commit 041be89

Please sign in to comment.