Skip to content

Commit

Permalink
perf: use fast path for cloning document with only primitive values re:
Browse files Browse the repository at this point in the history
  • Loading branch information
vkarpov15 committed Jul 8, 2024
1 parent 97a6ac4 commit 69de006
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 6 deletions.
35 changes: 33 additions & 2 deletions lib/document.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const getKeysInSchemaOrder = require('./helpers/schema/getKeysInSchemaOrder');
const getSubdocumentStrictValue = require('./helpers/schema/getSubdocumentStrictValue');
const handleSpreadDoc = require('./helpers/document/handleSpreadDoc');
const immediate = require('./helpers/immediate');
const isBsonType = require('./helpers/isBsonType');
const isDefiningProjection = require('./helpers/projection/isDefiningProjection');
const isExclusive = require('./helpers/projection/isExclusive');
const inspect = require('util').inspect;
Expand Down Expand Up @@ -3810,6 +3811,14 @@ Document.prototype.$toObject = function(options, json) {
// `clone` is necessary here because `utils.options` directly modifies the second input.
const defaultOptions = Object.assign({}, baseOptions, schemaOptions[path]);

const hasOnlyPrimitiveValues = !this.$__.populated && !this.$__.wasPopulated && (this._doc == null || Object.values(this._doc).every(v => {
return v == null
|| typeof v !== 'object'
|| (utils.isNativeObject(v) && !Array.isArray(v))
|| isBsonType(v, 'ObjectId')
|| isBsonType(v, 'Decimal128');
}));

// If options do not exist or is not an object, set it to empty object
options = utils.isPOJO(options) ? { ...options } : {};
options._calledWithOptions = options._calledWithOptions || { ...options };
Expand All @@ -3824,7 +3833,9 @@ Document.prototype.$toObject = function(options, json) {
}

options.minimize = _minimize;
options._seen = options._seen || new Map();
if (!hasOnlyPrimitiveValues) {
options._seen = options._seen || new Map();
}

const depopulate = options._calledWithOptions.depopulate
?? options._parentOptions?.depopulate
Expand Down Expand Up @@ -3853,7 +3864,27 @@ Document.prototype.$toObject = function(options, json) {
// to save it from being overwritten by sub-transform functions
// const originalTransform = options.transform;

let ret = clone(this._doc, options) || {};
let ret;
if (hasOnlyPrimitiveValues) {
// Fast path: if we don't have any nested objects or arrays, we only need a
// shallow clone.
ret = this._doc
? options.minimize ? (minimize({ ...this._doc }) || {}) : { ...this._doc }
: {};
if (!options.minimize || options.flattenObjectIds) {
const keys = Object.keys(ret);
for (const key of keys) {
// If `minimize` is false, still need to remove undefined keys
if (!options.minimize && ret[key] === undefined) {
delete ret[key];
} else if (options.flattenObjectIds && isBsonType(ret[key], 'ObjectId')) {
ret[key] = ret[key].toJSON();
}
}
}
} else {
ret = clone(this._doc, options) || {};
}

options._skipSingleNestedGetters = true;
const getters = options._calledWithOptions.getters
Expand Down
4 changes: 1 addition & 3 deletions test/document.unit.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,6 @@ describe('toObject()', function() {
it('doesnt crash with empty object (gh-3130)', function() {
const d = new Stub();
d._doc = undefined;
assert.doesNotThrow(function() {
d.toObject();
});
d.toObject();
});
});
1 change: 0 additions & 1 deletion test/model.populate.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9449,7 +9449,6 @@ describe('model: populate:', function() {
children: [{ type: 'ObjectId', ref: 'Child' }]
}));


const children = await Child.create([{ name: 'Luke' }, { name: 'Leia' }]);

let doc = await Parent.create({ children, child: children[0] });
Expand Down

0 comments on commit 69de006

Please sign in to comment.