Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 36 additions & 64 deletions lib/document.js
Original file line number Diff line number Diff line change
Expand Up @@ -3798,11 +3798,6 @@ Document.prototype.$__handleReject = function handleReject(err) {
*/

Document.prototype.$toObject = function(options, json) {
const defaultOptions = {
transform: true,
flattenDecimals: true
};

const path = json ? 'toJSON' : 'toObject';
const baseOptions = this.constructor &&
this.constructor.base &&
Expand All @@ -3811,7 +3806,7 @@ Document.prototype.$toObject = function(options, json) {
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.
Object.assign(defaultOptions, baseOptions, schemaOptions[path]);
const defaultOptions = Object.assign({}, baseOptions, schemaOptions[path]);

// If options do not exist or is not an object, set it to empty object
options = utils.isPOJO(options) ? { ...options } : {};
Expand All @@ -3826,45 +3821,17 @@ Document.prototype.$toObject = function(options, json) {
_minimize = schemaOptions.minimize;
}

let flattenMaps;
if (options._calledWithOptions.flattenMaps != null) {
flattenMaps = options.flattenMaps;
} else if (defaultOptions.flattenMaps != null) {
flattenMaps = defaultOptions.flattenMaps;
} else {
flattenMaps = schemaOptions.flattenMaps;
}

let flattenObjectIds;
if (options._calledWithOptions.flattenObjectIds != null) {
flattenObjectIds = options.flattenObjectIds;
} else if (defaultOptions.flattenObjectIds != null) {
flattenObjectIds = defaultOptions.flattenObjectIds;
} else {
flattenObjectIds = schemaOptions.flattenObjectIds;
}

// The original options that will be passed to `clone()`. Important because
// `clone()` will recursively call `$toObject()` on embedded docs, so we
// need the original options the user passed in, plus `_isNested` and
// `_parentOptions` for checking whether we need to depopulate.
const cloneOptions = {
...options,
_isNested: true,
json: json,
minimize: _minimize,
flattenMaps: flattenMaps,
flattenObjectIds: flattenObjectIds,
_seen: (options && options._seen) || new Map(),
_calledWithOptions: options._calledWithOptions
};
options.minimize = _minimize;
options._seen = options._seen || new Map();

const depopulate = options.depopulate ||
(options._parentOptions && options._parentOptions.depopulate || false);
const depopulate = options._calledWithOptions.depopulate
?? options._parentOptions?.depopulate
?? defaultOptions.depopulate
?? false;
// _isNested will only be true if this is not the top level document, we
// should never depopulate the top-level document
if (depopulate && options._isNested && this.$__.wasPopulated) {
return clone(this.$__.wasPopulated.value || this._id, cloneOptions);
return clone(this.$__.wasPopulated.value || this._id, options);
}

// merge default options with input options.
Expand All @@ -3877,33 +3844,47 @@ Document.prototype.$toObject = function(options, json) {
options.json = json;
options.minimize = _minimize;

cloneOptions._parentOptions = options;
options._parentOptions = options;

cloneOptions._skipSingleNestedGetters = false;
options._skipSingleNestedGetters = false;
// remember the root transform function
// to save it from being overwritten by sub-transform functions
const originalTransform = options.transform;
// const originalTransform = options.transform;

let ret = clone(this._doc, cloneOptions) || {};
let ret = clone(this._doc, options) || {};

cloneOptions._skipSingleNestedGetters = true;
if (options.getters) {
applyGetters(this, ret, cloneOptions);
options._skipSingleNestedGetters = true;
const getters = options._calledWithOptions.getters
?? options.getters
?? defaultOptions.getters
?? false;
if (getters) {
applyGetters(this, ret, options);

if (options.minimize) {
ret = minimize(ret) || {};
}
}

if (options.virtuals || (options.getters && options.virtuals !== false)) {
applyVirtuals(this, ret, cloneOptions, options);
const virtuals = options._calledWithOptions.virtuals
?? defaultOptions.virtuals
?? undefined;

if (virtuals || (getters && virtuals !== false)) {
applyVirtuals(this, ret, options, options);
}

if (options.versionKey === false && this.$__schema.options.versionKey) {
delete ret[this.$__schema.options.versionKey];
}

let transform = options.transform;
const transform = options._calledWithOptions.transform ?? true;
let transformFunction = undefined;
if (transform === true) {
transformFunction = defaultOptions.transform;
} else if (typeof transform === 'function') {
transformFunction = transform;
}

// In the case where a subdocument has its own transform function, we need to
// check and see if the parent has a transform (options.transform) and if the
Expand All @@ -3918,18 +3899,8 @@ Document.prototype.$toObject = function(options, json) {
omitDeselectedFields(this, ret);
}

if (transform === true || (schemaOptions.toObject && transform)) {
const opts = options.json ? schemaOptions.toJSON : schemaOptions.toObject;

if (opts) {
transform = (typeof options.transform === 'function' ? options.transform : opts.transform);
}
} else {
options.transform = originalTransform;
}

if (typeof transform === 'function') {
const xformed = transform(this, ret, options);
if (typeof transformFunction === 'function') {
const xformed = transformFunction(this, ret, options);
if (typeof xformed !== 'undefined') {
ret = xformed;
}
Expand Down Expand Up @@ -4146,7 +4117,8 @@ function applyVirtuals(self, json, options, toObjectOptions) {
assignPath = path.substring(options.path.length + 1);
}
if (assignPath.indexOf('.') === -1 && assignPath === path) {
v = clone(self.get(path, { noDottedPath: true }), options);
v = self.get(path, null, { noDottedPath: true });
v = clone(v, options);
if (v === void 0) {
continue;
}
Expand Down
2 changes: 1 addition & 1 deletion lib/helpers/clone.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ function clone(obj, options, isArrayChild) {
// Single nested subdocs should apply getters later in `applyGetters()`
// when calling `toObject()`. See gh-7442, gh-8295
if (options._skipSingleNestedGetters && obj.$isSingleNested) {
options = Object.assign({}, options, { getters: false });
options._calledWithOptions = Object.assign({}, options._calledWithOptions || {}, { getters: false });
}
if (options.retainDocuments && obj.$__ != null) {
const clonedDoc = obj.$clone();
Expand Down
22 changes: 15 additions & 7 deletions test/document.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -711,18 +711,19 @@ describe('document', function() {
name: String,
email: String
});
const topicSchema = new Schema({
title: String,
email: String,
followers: [userSchema]
});

userSchema.options.toObject = {
transform: function(doc, ret) {
delete ret.email;
}
};

const topicSchema = new Schema({
title: String,
email: String,
followers: [userSchema]
});

topicSchema.options.toObject = {
transform: function(doc, ret) {
ret.title = ret.title.toLowerCase();
Expand Down Expand Up @@ -833,7 +834,10 @@ describe('document', function() {
const userSchema = Schema({
firstName: String,
company: {
type: { companyId: { type: Schema.Types.ObjectId }, companyName: String }
type: {
companyId: { type: Schema.Types.ObjectId },
companyName: String
}
}
}, {
toObject: {
Expand All @@ -849,6 +853,7 @@ describe('document', function() {

const User = db.model('User', userSchema);
const user = new User({ firstName: 'test', company: { companyName: 'foo' } });
assert.equal(transformCalls.length, 0);
const obj = user.toObject();
assert.strictEqual(obj.company.details, 42);
assert.equal(transformCalls.length, 1);
Expand Down Expand Up @@ -1045,7 +1050,10 @@ describe('document', function() {
const userSchema = Schema({
firstName: String,
company: {
type: { companyId: { type: Schema.Types.ObjectId }, companyName: String }
type: {
companyId: { type: Schema.Types.ObjectId },
companyName: String
}
}
}, {
id: false,
Expand Down
2 changes: 1 addition & 1 deletion test/helpers/clone.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ describe('clone', () => {
$isSingleNested: true,
toObject(cloneOpts) {
assert.deepStrictEqual(
Object.assign({}, baseOpts, { getters: false }),
Object.assign({}, baseOpts),
cloneOpts
);
const obj = JSON.parse(JSON.stringify(base));
Expand Down