Skip to content

Commit 7e7dc1a

Browse files
authored
Merge pull request #15336 from Automattic/vkarpov15/gh-15313
feat(subdocument): support schematype-level minimize option to disable minimizing empty subdocuments
2 parents ca018db + 5853a8b commit 7e7dc1a

File tree

7 files changed

+62
-6
lines changed

7 files changed

+62
-6
lines changed

Diff for: lib/document.js

+2
Original file line numberDiff line numberDiff line change
@@ -3818,6 +3818,8 @@ Document.prototype.$toObject = function(options, json) {
38183818
let _minimize;
38193819
if (options._calledWithOptions.minimize != null) {
38203820
_minimize = options.minimize;
3821+
} else if (this.$__schemaTypeOptions?.minimize != null) {
3822+
_minimize = this.$__schemaTypeOptions.minimize;
38213823
} else if (defaultOptions != null && defaultOptions.minimize != null) {
38223824
_minimize = defaultOptions.minimize;
38233825
} else {

Diff for: lib/helpers/clone.js

-5
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ function clone(obj, options, isArrayChild) {
5757
return clonedDoc;
5858
}
5959
}
60-
const isSingleNested = obj.$isSingleNested;
6160

6261
if (isPOJO(obj) && obj.$__ != null && obj._doc != null) {
6362
return obj._doc;
@@ -70,10 +69,6 @@ function clone(obj, options, isArrayChild) {
7069
ret = obj.toObject(options);
7170
}
7271

73-
if (options && options.minimize && !obj.constructor.$__required && isSingleNested && Object.keys(ret).length === 0) {
74-
return undefined;
75-
}
76-
7772
return ret;
7873
}
7974

Diff for: lib/options/schemaSubdocumentOptions.js

+25-1
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,36 @@ const opts = require('./propertyOptions');
3131
* parentSchema.path('child').schema.options._id; // false
3232
*
3333
* @api public
34-
* @property of
34+
* @property _id
3535
* @memberOf SchemaSubdocumentOptions
3636
* @type {Function|string}
3737
* @instance
3838
*/
3939

4040
Object.defineProperty(SchemaSubdocumentOptions.prototype, '_id', opts);
4141

42+
/**
43+
* If set, overwrites the child schema's `minimize` option. In addition, configures whether the entire
44+
* subdocument can be minimized out.
45+
*
46+
* #### Example:
47+
*
48+
* const childSchema = Schema({ name: String });
49+
* const parentSchema = Schema({
50+
* child: { type: childSchema, minimize: false }
51+
* });
52+
* const ParentModel = mongoose.model('Parent', parentSchema);
53+
* // Saves `{ child: {} }` to the db. Without `minimize: false`, Mongoose would remove the empty
54+
* // object and save `{}` to the db.
55+
* await ParentModel.create({ child: {} });
56+
*
57+
* @api public
58+
* @property minimize
59+
* @memberOf SchemaSubdocumentOptions
60+
* @type {Function|string}
61+
* @instance
62+
*/
63+
64+
Object.defineProperty(SchemaSubdocumentOptions.prototype, 'minimize', opts);
65+
4266
module.exports = SchemaSubdocumentOptions;

Diff for: lib/schema/subdocument.js

+1
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ function _createConstructor(schema, baseClass, options) {
9090
_embedded.prototype = Object.create(proto);
9191
_embedded.prototype.$__setSchema(schema);
9292
_embedded.prototype.constructor = _embedded;
93+
_embedded.prototype.$__schemaTypeOptions = options;
9394
_embedded.$__required = options?.required;
9495
_embedded.base = schema.base;
9596
_embedded.schema = schema;

Diff for: lib/types/subdocument.js

+19
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,25 @@ if (util.inspect.custom) {
412412
Subdocument.prototype[util.inspect.custom] = Subdocument.prototype.inspect;
413413
}
414414

415+
/**
416+
* Override `$toObject()` to handle minimizing the whole path. Should not minimize if schematype-level minimize
417+
* is set to false re: gh-11247, gh-14058, gh-14151
418+
*/
419+
420+
Subdocument.prototype.$toObject = function $toObject(options, json) {
421+
const ret = Document.prototype.$toObject.call(this, options, json);
422+
423+
// If `$toObject()` was called recursively, respect the minimize option, including schematype level minimize.
424+
// If minimize is set, then we can minimize out the whole object.
425+
if (Object.keys(ret).length === 0 && options?._calledWithOptions != null) {
426+
const minimize = options._calledWithOptions?.minimize ?? this?.$__schemaTypeOptions?.minimize ?? options.minimize;
427+
if (minimize && !this.constructor.$__required) {
428+
return undefined;
429+
}
430+
}
431+
return ret;
432+
};
433+
415434
/**
416435
* Registers remove event listeners for triggering
417436
* on subdocuments.

Diff for: test/types.subdocument.test.js

+12
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,16 @@ describe('types.subdocument', function() {
9797
assert.ok(doc.child.isModified('id'));
9898
});
9999
});
100+
101+
it('respects schematype-level minimize (gh-15313)', function() {
102+
const MySubSchema = new Schema({}, { strict: false, _id: false });
103+
const MySchema = new Schema({ myfield: { type: MySubSchema, minimize: false } });
104+
const MyModel = db.model('MyModel', MySchema);
105+
106+
const doc = new MyModel({ myfield: {} });
107+
assert.deepStrictEqual(doc.toObject().myfield, {});
108+
109+
const doc2 = new MyModel({ myfield: { empty: {} } });
110+
assert.deepStrictEqual(doc2.toObject().myfield, { empty: {} });
111+
});
100112
});

Diff for: types/schematypes.d.ts

+3
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,9 @@ declare module 'mongoose' {
173173
/** The maximum value allowed for this path. Only allowed for numbers and dates. */
174174
max?: number | NativeDate | [number, string] | [NativeDate, string] | readonly [number, string] | readonly [NativeDate, string];
175175

176+
/** Set to false to disable minimizing empty single nested subdocuments by default */
177+
minimize?: boolean;
178+
176179
/** Defines a TTL index on this path. Only allowed for dates. */
177180
expires?: string | number;
178181

0 commit comments

Comments
 (0)