Skip to content

Commit 8f4070a

Browse files
authored
Merge pull request #14844 from Automattic/vkarpov15/gh-14840
fix(document): avoid unnecessary clone() in `applyGetters()` that was triggering additional getters
2 parents 4b1aadc + 6356c5b commit 8f4070a

File tree

3 files changed

+37
-12
lines changed

3 files changed

+37
-12
lines changed

lib/document.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3856,7 +3856,6 @@ Document.prototype.$toObject = function(options, json) {
38563856
// Parent options should only bubble down for subdocuments, not populated docs
38573857
options._parentOptions = this.$isSubdocument ? options : null;
38583858

3859-
options._skipSingleNestedGetters = false;
38603859
// remember the root transform function
38613860
// to save it from being overwritten by sub-transform functions
38623861
// const originalTransform = options.transform;
@@ -3870,13 +3869,13 @@ Document.prototype.$toObject = function(options, json) {
38703869
ret = clone(this._doc, options) || {};
38713870
}
38723871

3873-
options._skipSingleNestedGetters = true;
38743872
const getters = options._calledWithOptions.getters
38753873
?? options.getters
38763874
?? defaultOptions.getters
38773875
?? false;
3876+
38783877
if (getters) {
3879-
applyGetters(this, ret, options);
3878+
applyGetters(this, ret);
38803879

38813880
if (options.minimize) {
38823881
ret = minimize(ret) || {};
@@ -4187,12 +4186,11 @@ function applyVirtuals(self, json, options, toObjectOptions) {
41874186
*
41884187
* @param {Document} self
41894188
* @param {Object} json
4190-
* @param {Object} [options]
41914189
* @return {Object} `json`
41924190
* @api private
41934191
*/
41944192

4195-
function applyGetters(self, json, options) {
4193+
function applyGetters(self, json) {
41964194
const schema = self.$__schema;
41974195
const paths = Object.keys(schema.paths);
41984196
let i = paths.length;
@@ -4228,8 +4226,10 @@ function applyGetters(self, json, options) {
42284226
if (branch != null && typeof branch !== 'object') {
42294227
break;
42304228
} else if (ii === last) {
4231-
const val = self.$get(path);
4232-
branch[part] = clone(val, options);
4229+
branch[part] = schema.paths[path].applyGetters(
4230+
branch[part],
4231+
self
4232+
);
42334233
if (Array.isArray(branch[part]) && schema.paths[path].$embeddedSchemaType) {
42344234
for (let i = 0; i < branch[part].length; ++i) {
42354235
branch[part][i] = schema.paths[path].$embeddedSchemaType.applyGetters(

lib/helpers/clone.js

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,6 @@ function clone(obj, options, isArrayChild) {
4040

4141
if (isMongooseObject(obj)) {
4242
if (options) {
43-
// Single nested subdocs should apply getters later in `applyGetters()`
44-
// when calling `toObject()`. See gh-7442, gh-8295
45-
if (options._skipSingleNestedGetters && obj.$isSingleNested) {
46-
options._calledWithOptions = Object.assign({}, options._calledWithOptions || {}, { getters: false });
47-
}
4843
if (options.retainDocuments && obj.$__ != null) {
4944
const clonedDoc = obj.$clone();
5045
if (obj.__index != null) {

test/document.test.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13843,6 +13843,36 @@ describe('document', function() {
1384313843
assert.strictEqual(requiredCalls[0], doc.config.prop);
1384413844
assert.strictEqual(requiredCalls[1], doc.config.prop);
1384513845
});
13846+
13847+
it('applies toObject() getters to 3 level deep subdocuments (gh-14840) (gh-14835)', async function() {
13848+
// Define nested schemas
13849+
const Level3Schema = new mongoose.Schema({
13850+
property: {
13851+
type: String,
13852+
get: (value) => value ? value.toUpperCase() : value
13853+
}
13854+
});
13855+
13856+
const Level2Schema = new mongoose.Schema({ level3: Level3Schema });
13857+
const Level1Schema = new mongoose.Schema({ level2: Level2Schema });
13858+
const MainSchema = new mongoose.Schema({ level1: Level1Schema });
13859+
const MainModel = db.model('Test', MainSchema);
13860+
13861+
const doc = await MainModel.create({
13862+
level1: {
13863+
level2: {
13864+
level3: {
13865+
property: 'testValue'
13866+
}
13867+
}
13868+
}
13869+
});
13870+
13871+
// Fetch and convert the document to an object with getters applied
13872+
const result = await MainModel.findById(doc._id);
13873+
const objectWithGetters = result.toObject({ getters: true, virtuals: false });
13874+
assert.strictEqual(objectWithGetters.level1.level2.level3.property, 'TESTVALUE');
13875+
});
1384613876
});
1384713877

1384813878
describe('Check if instance function that is supplied in schema option is available', function() {

0 commit comments

Comments
 (0)