Skip to content

Commit 0502d2b

Browse files
authored
Merge pull request #14672 from Automattic/vkarpov15/gh-14394-memoize-defaultoptions
perf: memoize toJSON / toObject default options
2 parents 174d8be + 4f645ed commit 0502d2b

File tree

7 files changed

+59
-37
lines changed

7 files changed

+59
-37
lines changed

lib/document.js

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ const clone = require('./helpers/clone');
2222
const compile = require('./helpers/document/compile').compile;
2323
const defineKey = require('./helpers/document/compile').defineKey;
2424
const flatten = require('./helpers/common').flatten;
25-
const get = require('./helpers/get');
2625
const getEmbeddedDiscriminatorPath = require('./helpers/document/getEmbeddedDiscriminatorPath');
2726
const getKeysInSchemaOrder = require('./helpers/schema/getKeysInSchemaOrder');
2827
const getSubdocumentStrictValue = require('./helpers/schema/getSubdocumentStrictValue');
@@ -3798,15 +3797,7 @@ Document.prototype.$__handleReject = function handleReject(err) {
37983797
*/
37993798

38003799
Document.prototype.$toObject = function(options, json) {
3801-
const path = json ? 'toJSON' : 'toObject';
3802-
const baseOptions = this.constructor &&
3803-
this.constructor.base &&
3804-
this.constructor.base.options &&
3805-
get(this.constructor.base.options, path) || {};
3806-
const schemaOptions = this.$__schema && this.$__schema.options || {};
3807-
// merge base default options with Schema's set default options if available.
3808-
// `clone` is necessary here because `utils.options` directly modifies the second input.
3809-
const defaultOptions = Object.assign({}, baseOptions, schemaOptions[path]);
3800+
const defaultOptions = this.$__schema._defaultToObjectOptions(json);
38103801

38113802
// If options do not exist or is not an object, set it to empty object
38123803
options = utils.isPOJO(options) ? { ...options } : {};
@@ -3815,18 +3806,18 @@ Document.prototype.$toObject = function(options, json) {
38153806
let _minimize;
38163807
if (options._calledWithOptions.minimize != null) {
38173808
_minimize = options.minimize;
3818-
} else if (defaultOptions.minimize != null) {
3809+
} else if (defaultOptions != null && defaultOptions.minimize != null) {
38193810
_minimize = defaultOptions.minimize;
38203811
} else {
3821-
_minimize = schemaOptions.minimize;
3812+
_minimize = this.$__schema.options.minimize;
38223813
}
38233814

38243815
options.minimize = _minimize;
38253816
options._seen = options._seen || new Map();
38263817

38273818
const depopulate = options._calledWithOptions.depopulate
38283819
?? options._parentOptions?.depopulate
3829-
?? defaultOptions.depopulate
3820+
?? defaultOptions?.depopulate
38303821
?? false;
38313822
// _isNested will only be true if this is not the top level document, we
38323823
// should never depopulate the top-level document
@@ -3835,9 +3826,11 @@ Document.prototype.$toObject = function(options, json) {
38353826
}
38363827

38373828
// merge default options with input options.
3838-
for (const key of Object.keys(defaultOptions)) {
3839-
if (options[key] == null) {
3840-
options[key] = defaultOptions[key];
3829+
if (defaultOptions != null) {
3830+
for (const key of Object.keys(defaultOptions)) {
3831+
if (options[key] == null) {
3832+
options[key] = defaultOptions[key];
3833+
}
38413834
}
38423835
}
38433836
options._isNested = true;
@@ -4118,10 +4111,10 @@ function applyVirtuals(self, json, options, toObjectOptions) {
41184111
}
41194112
if (assignPath.indexOf('.') === -1 && assignPath === path) {
41204113
v = self.get(path, null, { noDottedPath: true });
4121-
v = clone(v, options);
41224114
if (v === void 0) {
41234115
continue;
41244116
}
4117+
v = clone(v, options);
41254118
json[assignPath] = v;
41264119
continue;
41274120
}

lib/model.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5105,6 +5105,8 @@ Model.recompileSchema = function recompileSchema() {
51055105
}
51065106
}
51075107

5108+
delete this.schema._defaultToObjectOptionsMap;
5109+
51085110
applyEmbeddedDiscriminators(this.schema, new WeakSet(), true);
51095111
};
51105112

lib/schema.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -643,6 +643,30 @@ Schema.prototype.discriminator = function(name, schema, options) {
643643
return this;
644644
};
645645

646+
/*!
647+
* Get this schema's default toObject/toJSON options, including Mongoose global
648+
* options.
649+
*/
650+
651+
Schema.prototype._defaultToObjectOptions = function(json) {
652+
const path = json ? 'toJSON' : 'toObject';
653+
if (this._defaultToObjectOptionsMap && this._defaultToObjectOptionsMap[path]) {
654+
return this._defaultToObjectOptionsMap[path];
655+
}
656+
657+
const baseOptions = this.base &&
658+
this.base.options &&
659+
this.base.options[path] || {};
660+
const schemaOptions = this.options[path] || {};
661+
// merge base default options with Schema's set default options if available.
662+
// `clone` is necessary here because `utils.options` directly modifies the second input.
663+
const defaultOptions = Object.assign({}, baseOptions, schemaOptions);
664+
665+
this._defaultToObjectOptionsMap = this._defaultToObjectOptionsMap || {};
666+
this._defaultToObjectOptionsMap[path] = defaultOptions;
667+
return defaultOptions;
668+
};
669+
646670
/**
647671
* Adds key path / schema type pairs to this schema.
648672
*

test/document.test.js

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -100,15 +100,6 @@ schema.path('date').set(function(v) {
100100
return v;
101101
});
102102

103-
/**
104-
* Method subject to hooks. Simply fires the callback once the hooks are
105-
* executed.
106-
*/
107-
108-
TestDocument.prototype.hooksTest = function(fn) {
109-
fn(null, arguments);
110-
};
111-
112103
const childSchema = new Schema({ counter: Number });
113104

114105
const parentSchema = new Schema({
@@ -433,9 +424,10 @@ describe('document', function() {
433424
delete ret.oids;
434425
ret._id = ret._id.toString();
435426
};
427+
delete doc.schema._defaultToObjectOptionsMap;
436428
clone = doc.toObject();
437429
assert.equal(doc.id, clone._id);
438-
assert.ok(undefined === clone.em);
430+
assert.strictEqual(clone.em, undefined);
439431
assert.ok(undefined === clone.numbers);
440432
assert.ok(undefined === clone.oids);
441433
assert.equal(clone.test, 'test');
@@ -452,6 +444,7 @@ describe('document', function() {
452444
return { myid: ret._id.toString() };
453445
};
454446

447+
delete doc.schema._defaultToObjectOptionsMap;
455448
clone = doc.toObject();
456449
assert.deepEqual(out, clone);
457450

@@ -489,6 +482,7 @@ describe('document', function() {
489482

490483
// all done
491484
delete doc.schema.options.toObject;
485+
delete doc.schema._defaultToObjectOptionsMap;
492486
});
493487

494488
it('toObject transform', async function() {
@@ -884,6 +878,7 @@ describe('document', function() {
884878
};
885879

886880
doc.schema.options.toJSON = { virtuals: true };
881+
delete doc.schema._defaultToObjectOptionsMap;
887882
let clone = doc.toJSON();
888883
assert.equal(clone.test, 'test');
889884
assert.ok(clone.oids instanceof Array);
@@ -897,6 +892,7 @@ describe('document', function() {
897892
delete path.casterConstructor.prototype.toJSON;
898893

899894
doc.schema.options.toJSON = { minimize: false };
895+
delete doc.schema._defaultToObjectOptionsMap;
900896
clone = doc.toJSON();
901897
assert.equal(clone.nested2.constructor.name, 'Object');
902898
assert.equal(Object.keys(clone.nested2).length, 1);
@@ -932,6 +928,7 @@ describe('document', function() {
932928
ret._id = ret._id.toString();
933929
};
934930

931+
delete doc.schema._defaultToObjectOptionsMap;
935932
clone = doc.toJSON();
936933
assert.equal(clone._id, doc.id);
937934
assert.ok(undefined === clone.em);
@@ -951,6 +948,7 @@ describe('document', function() {
951948
return { myid: ret._id.toString() };
952949
};
953950

951+
delete doc.schema._defaultToObjectOptionsMap;
954952
clone = doc.toJSON();
955953
assert.deepEqual(out, clone);
956954

@@ -988,6 +986,7 @@ describe('document', function() {
988986

989987
// all done
990988
delete doc.schema.options.toJSON;
989+
delete doc.schema._defaultToObjectOptionsMap;
991990
});
992991

993992
it('jsonifying an object', function() {
@@ -998,7 +997,7 @@ describe('document', function() {
998997
// parse again
999998
const obj = JSON.parse(json);
1000999

1001-
assert.equal(obj.test, 'woot');
1000+
assert.equal(obj.test, 'woot', JSON.stringify(obj));
10021001
assert.equal(obj._id, oidString);
10031002
});
10041003

test/document.unit.test.js

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
'use strict';
66

7+
const Schema = require('../lib/schema');
78
const start = require('./common');
89

910
const assert = require('assert');
@@ -37,12 +38,9 @@ describe('toObject()', function() {
3738

3839
beforeEach(function() {
3940
Stub = function() {
40-
const schema = this.$__schema = {
41-
options: { toObject: { minimize: false, virtuals: true } },
42-
virtuals: { virtual: 'test' }
43-
};
41+
this.$__schema = new Schema({}, { toObject: { minimize: false, virtuals: true } });
42+
this.$__schema.virtual('virtual').get(function() { return 'test'; });
4443
this._doc = { empty: {} };
45-
this.get = function(path) { return schema.virtuals[path]; };
4644
this.$__ = {};
4745
};
4846
Stub.prototype = Object.create(mongoose.Document.prototype);

test/index.test.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ describe('mongoose module:', function() {
215215

216216
mongoose.set('toJSON', { virtuals: true });
217217

218-
const schema = new Schema({});
218+
const schema = new mongoose.Schema({});
219219
schema.virtual('foo').get(() => 42);
220220
const M = mongoose.model('Test', schema);
221221

@@ -225,7 +225,7 @@ describe('mongoose module:', function() {
225225

226226
assert.equal(doc.toJSON({ virtuals: false }).foo, void 0);
227227

228-
const schema2 = new Schema({}, { toJSON: { virtuals: true } });
228+
const schema2 = new mongoose.Schema({}, { toJSON: { virtuals: true } });
229229
schema2.virtual('foo').get(() => 'bar');
230230
const M2 = mongoose.model('Test2', schema2);
231231

@@ -239,7 +239,7 @@ describe('mongoose module:', function() {
239239

240240
mongoose.set('toObject', { virtuals: true });
241241

242-
const schema = new Schema({});
242+
const schema = new mongoose.Schema({});
243243
schema.virtual('foo').get(() => 42);
244244
const M = mongoose.model('Test', schema);
245245

test/model.test.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7445,7 +7445,7 @@ describe('Model', function() {
74457445
});
74467446

74477447
it('supports recompiling model with new schema additions (gh-14296)', function() {
7448-
const schema = new mongoose.Schema({ field: String });
7448+
const schema = new mongoose.Schema({ field: String }, { toObject: { virtuals: false } });
74497449
const TestModel = db.model('Test', schema);
74507450
TestModel.schema.virtual('myVirtual').get(function() {
74517451
return this.field + ' from myVirtual';
@@ -7455,6 +7455,12 @@ describe('Model', function() {
74557455

74567456
TestModel.recompileSchema();
74577457
assert.equal(doc.myVirtual, 'Hello from myVirtual');
7458+
assert.strictEqual(doc.toObject().myVirtual, undefined);
7459+
7460+
doc.schema.options.toObject.virtuals = true;
7461+
TestModel.recompileSchema();
7462+
assert.equal(doc.myVirtual, 'Hello from myVirtual');
7463+
assert.equal(doc.toObject().myVirtual, 'Hello from myVirtual');
74587464
});
74597465

74607466
it('supports recompiling model with new discriminators (gh-14444) (gh-14296)', function() {

0 commit comments

Comments
 (0)