diff --git a/lib/cast.js b/lib/cast.js index d5ea75227b9..f91cdb007dc 100644 --- a/lib/cast.js +++ b/lib/cast.js @@ -79,7 +79,7 @@ module.exports = function cast(schema, obj, options, context) { val[k] = cast(discriminatorSchema ? discriminatorSchema : schema, val[k], options, context); } - if (Object.keys(val[k]).length === 0 && beforeCastKeysLength !== 0) { + if (utils.hasOwnKeys(val[k]) === false && beforeCastKeysLength !== 0) { val.splice(k, 1); } } @@ -126,10 +126,7 @@ module.exports = function cast(schema, obj, options, context) { const pathFirstHalf = split.slice(0, j).join('.'); const pathLastHalf = split.slice(j).join('.'); const _schematype = schema.path(pathFirstHalf); - const discriminatorKey = _schematype && - _schematype.schema && - _schematype.schema.options && - _schematype.schema.options.discriminatorKey; + const discriminatorKey = _schematype?.schema?.options?.discriminatorKey; // gh-6027: if we haven't found the schematype but this path is // underneath an embedded discriminator and the embedded discriminator @@ -430,11 +427,7 @@ function getStrictQuery(queryOptions, schemaUserProvidedOptions, schemaOptions, if ('strictQuery' in schemaUserProvidedOptions) { return schemaUserProvidedOptions.strictQuery; } - const mongooseOptions = context && - context.mongooseCollection && - context.mongooseCollection.conn && - context.mongooseCollection.conn.base && - context.mongooseCollection.conn.base.options; + const mongooseOptions = context?.mongooseCollection?.conn?.base?.options; if (mongooseOptions) { if ('strictQuery' in mongooseOptions) { return mongooseOptions.strictQuery; diff --git a/lib/connection.js b/lib/connection.js index 8d9d27e8ff0..d31138e63cb 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -630,7 +630,7 @@ Connection.prototype.createCollections = async function createCollections(option } } - if (!continueOnError && Object.keys(errorsMap).length) { + if (!continueOnError && utils.hasOwnKeys(errorsMap)) { const message = Object.entries(errorsMap).map(([modelName, err]) => `${modelName}: ${err.message}`).join(', '); const createCollectionsError = new CreateCollectionsError(message, errorsMap); throw createCollectionsError; @@ -1250,8 +1250,11 @@ Connection.prototype._close = async function _close(force, destroy) { const conn = this; switch (this.readyState) { case STATES.disconnected: - if (destroy && this.base.connections.indexOf(conn) !== -1) { - this.base.connections.splice(this.base.connections.indexOf(conn), 1); + if (destroy) { + const index = this.base.connections.indexOf(conn); + if (index !== -1) { + this.base.connections.splice(index, 1); + } } if (!closeCalled) { await this.doClose(force); @@ -1262,8 +1265,11 @@ Connection.prototype._close = async function _close(force, destroy) { case STATES.connected: this.readyState = STATES.disconnecting; await this.doClose(force); - if (destroy && _this.base.connections.indexOf(conn) !== -1) { - this.base.connections.splice(this.base.connections.indexOf(conn), 1); + if (destroy) { + const index = _this.base.connections.indexOf(conn); + if (index !== -1) { + this.base.connections.splice(index, 1); + } } this.onClose(force); @@ -1772,7 +1778,7 @@ Connection.prototype.syncIndexes = async function syncIndexes(options = {}) { } } - if (!continueOnError && Object.keys(errorsMap).length) { + if (!continueOnError && utils.hasOwnKeys(errorsMap)) { const message = Object.entries(errorsMap).map(([modelName, err]) => `${modelName}: ${err.message}`).join(', '); const syncIndexesError = new SyncIndexesError(message, errorsMap); throw syncIndexesError; diff --git a/lib/cursor/changeStream.js b/lib/cursor/changeStream.js index b41e2379e83..3221a29fb9c 100644 --- a/lib/cursor/changeStream.js +++ b/lib/cursor/changeStream.js @@ -83,7 +83,7 @@ class ChangeStream extends EventEmitter { driverChangeStreamEvents.forEach(ev => { this.driverChangeStream.on(ev, data => { - if (data != null && data.fullDocument != null && this.options && this.options.hydrate) { + if (data?.fullDocument != null && this.options?.hydrate) { data.fullDocument = this.options.model.hydrate(data.fullDocument); } this.emit(ev, data); @@ -102,7 +102,7 @@ class ChangeStream extends EventEmitter { driverChangeStreamEvents.forEach(ev => { this.driverChangeStream.on(ev, data => { - if (data != null && data.fullDocument != null && this.options && this.options.hydrate) { + if (data?.fullDocument != null && this.options?.hydrate) { data.fullDocument = this.options.model.hydrate(data.fullDocument); } this.emit(ev, data); diff --git a/lib/document.js b/lib/document.js index 45da5eb3f13..2a5f97b1219 100644 --- a/lib/document.js +++ b/lib/document.js @@ -149,7 +149,7 @@ function Document(obj, fields, options) { // determine if this doc is a result of a query with // excluded fields - if (utils.isPOJO(fields) && Object.keys(fields).length > 0) { + if (utils.isPOJO(fields) && utils.hasOwnKeys(fields)) { exclude = isExclusive(fields); this.$__.selected = fields; this.$__.exclude = exclude; @@ -1015,17 +1015,21 @@ Document.prototype.$timestamps = function $timestamps(value) { */ Document.prototype.overwrite = function overwrite(obj) { - const keys = Array.from(new Set(Object.keys(this._doc).concat(Object.keys(obj)))); + const keys = new Set(Object.keys(this._doc)); + for (const key of Object.keys(obj)) { + keys.add(key); + } + const schemaOptions = this.$__schema.options; for (const key of keys) { if (key === '_id') { continue; } // Explicitly skip version key - if (this.$__schema.options.versionKey && key === this.$__schema.options.versionKey) { + if (schemaOptions.versionKey && key === schemaOptions.versionKey) { continue; } - if (this.$__schema.options.discriminatorKey && key === this.$__schema.options.discriminatorKey) { + if (schemaOptions.discriminatorKey && key === schemaOptions.discriminatorKey) { continue; } this.$set(key, obj[key]); @@ -1697,7 +1701,7 @@ Document.prototype.$__set = function(pathToMark, path, options, constructing, pa if (shouldModify) { if (this.$__.primitiveAtomics && this.$__.primitiveAtomics[path]) { delete this.$__.primitiveAtomics[path]; - if (Object.keys(this.$__.primitiveAtomics).length === 0) { + if (utils.hasOwnKeys(this.$__.primitiveAtomics) === false) { delete this.$__.primitiveAtomics; } } @@ -3001,7 +3005,7 @@ Document.prototype.$__validate = async function $__validate(pathsToValidate, opt delete validationError.errors[errPath]; } } - if (Object.keys(validationError.errors).length === 0) { + if (utils.hasOwnKeys(validationError.errors) === false) { validationError = void 0; } } @@ -3402,7 +3406,7 @@ Document.prototype.$markValid = function(path) { } delete this.$__.validationError.errors[path]; - if (Object.keys(this.$__.validationError.errors).length === 0) { + if (utils.hasOwnKeys(this.$__.validationError.errors) === false) { this.$__.validationError = null; } }; @@ -3422,7 +3426,7 @@ function _markValidSubpaths(doc, path) { delete doc.$__.validationError.errors[key]; } } - if (Object.keys(doc.$__.validationError.errors).length === 0) { + if (utils.hasOwnKeys(doc.$__.validationError.errors) === false) { doc.$__.validationError = null; } } @@ -3497,7 +3501,7 @@ function _checkImmutableSubpaths(subdoc, schematype, priorVal) { */ Document.prototype.$isValid = function(path) { - if (this.$__.validationError == null || Object.keys(this.$__.validationError.errors).length === 0) { + if (this.$__.validationError == null || utils.hasOwnKeys(this.$__.validationError.errors) === false) { return true; } if (path == null) { @@ -4375,7 +4379,7 @@ function omitDeselectedFields(self, json) { selected = {}; queryhelpers.applyPaths(selected, schema); } - if (selected == null || Object.keys(selected).length === 0) { + if (selected == null || utils.hasOwnKeys(selected) === false) { return json; } @@ -5057,7 +5061,7 @@ Document.prototype.$__delta = function $__delta() { this.$__version(where, delta); } - if (Object.keys(delta).length === 0) { + if (utils.hasOwnKeys(delta) === false) { return [where, null]; } @@ -5109,7 +5113,7 @@ function checkDivergentArray(doc, path, array) { if (check) { const atomics = array[arrayAtomicsSymbol]; - if (Object.keys(atomics).length === 0 || atomics.$set || atomics.$pop) { + if (utils.hasOwnKeys(atomics) === false || atomics.$set || atomics.$pop) { return path; } } diff --git a/lib/drivers/node-mongodb-native/collection.js b/lib/drivers/node-mongodb-native/collection.js index 1e4678ff93f..cf25c74ddc6 100644 --- a/lib/drivers/node-mongodb-native/collection.js +++ b/lib/drivers/node-mongodb-native/collection.js @@ -97,15 +97,8 @@ function iter(i) { const collection = this._getCollection(); const args = Array.from(arguments); const _this = this; - const globalDebug = _this && - _this.conn && - _this.conn.base && - _this.conn.base.options && - _this.conn.base.options.debug; - const connectionDebug = _this && - _this.conn && - _this.conn.options && - _this.conn.options.debug; + const globalDebug = _this?.conn?.base?.options?.debug; + const connectionDebug = _this?.conn?.options?.debug; const debug = connectionDebug == null ? globalDebug : connectionDebug; const lastArg = arguments[arguments.length - 1]; const opId = new ObjectId(); @@ -428,12 +421,7 @@ function format(obj, sub, color, shell) { formatDate(x, key, shell); } else if (_constructorName === 'ClientSession') { x[key] = inspectable('ClientSession("' + - ( - x[key] && - x[key].id && - x[key].id.id && - x[key].id.id.buffer || '' - ).toString('hex') + '")'); + (x[key]?.id?.id?.buffer || '').toString('hex') + '")'); } else if (Array.isArray(x[key])) { x[key] = x[key].map(map); } else if (error != null) { diff --git a/lib/drivers/node-mongodb-native/connection.js b/lib/drivers/node-mongodb-native/connection.js index 3f4be863c2e..6ff3e03d91b 100644 --- a/lib/drivers/node-mongodb-native/connection.js +++ b/lib/drivers/node-mongodb-native/connection.js @@ -316,15 +316,15 @@ NativeConnection.prototype.createClient = async function createClient(uri, optio const { schemaMap, encryptedFieldsMap } = this._buildEncryptionSchemas(); - if ((Object.keys(schemaMap).length > 0 || Object.keys(encryptedFieldsMap).length) && !options.autoEncryption) { + if ((utils.hasOwnKeys(schemaMap) || utils.hasOwnKeys(encryptedFieldsMap)) && !options.autoEncryption) { throw new Error('Must provide `autoEncryption` when connecting with encrypted schemas.'); } - if (Object.keys(schemaMap).length > 0) { + if (utils.hasOwnKeys(schemaMap)) { options.autoEncryption.schemaMap = schemaMap; } - if (Object.keys(encryptedFieldsMap).length > 0) { + if (utils.hasOwnKeys(encryptedFieldsMap)) { options.autoEncryption.encryptedFieldsMap = encryptedFieldsMap; } @@ -435,18 +435,8 @@ function _setClient(conn, client, options, dbName) { const db = dbName != null ? client.db(dbName) : client.db(); conn.db = db; conn.client = client; - conn.host = client && - client.s && - client.s.options && - client.s.options.hosts && - client.s.options.hosts[0] && - client.s.options.hosts[0].host || void 0; - conn.port = client && - client.s && - client.s.options && - client.s.options.hosts && - client.s.options.hosts[0] && - client.s.options.hosts[0].port || void 0; + conn.host = client?.s?.options?.hosts?.[0]?.host; + conn.port = client?.s?.options?.hosts?.[0]?.port; conn.name = dbName != null ? dbName : db.databaseName; conn._closeCalled = client._closeCalled; @@ -463,10 +453,7 @@ function _setClient(conn, client, options, dbName) { } }; - const type = client && - client.topology && - client.topology.description && - client.topology.description.type || ''; + const type = client?.topology?.description?.type || ''; if (type === 'Single') { client.on('serverDescriptionChanged', ev => { diff --git a/lib/helpers/document/applyTimestamps.js b/lib/helpers/document/applyTimestamps.js index 425e144c867..c26146bfa1c 100644 --- a/lib/helpers/document/applyTimestamps.js +++ b/lib/helpers/document/applyTimestamps.js @@ -2,6 +2,7 @@ const handleTimestampOption = require('../schema/handleTimestampOption'); const mpath = require('mpath'); +const utils = require('../../utils'); module.exports = applyTimestamps; @@ -71,7 +72,7 @@ function applyTimestampsToDoc(schema, obj, options) { return; } - if (schema.discriminators && Object.keys(schema.discriminators).length > 0) { + if (schema.discriminators && utils.hasOwnKeys(schema.discriminators)) { for (const discriminatorKey of Object.keys(schema.discriminators)) { const discriminator = schema.discriminators[discriminatorKey]; const key = discriminator.discriminatorMapping.key; diff --git a/lib/helpers/document/applyVirtuals.js b/lib/helpers/document/applyVirtuals.js index 5fbe7ca82ba..09a61f8a2c5 100644 --- a/lib/helpers/document/applyVirtuals.js +++ b/lib/helpers/document/applyVirtuals.js @@ -1,6 +1,7 @@ 'use strict'; const mpath = require('mpath'); +const utils = require('../../utils'); module.exports = applyVirtuals; @@ -101,7 +102,7 @@ function applyVirtualsToDoc(schema, obj, virtuals) { return; } - if (schema.discriminators && Object.keys(schema.discriminators).length > 0) { + if (schema.discriminators && utils.hasOwnKeys(schema.discriminators)) { for (const discriminatorKey of Object.keys(schema.discriminators)) { const discriminator = schema.discriminators[discriminatorKey]; const key = discriminator.discriminatorMapping.key; diff --git a/lib/helpers/document/compile.js b/lib/helpers/document/compile.js index 51721e7a490..4b4350f4c2e 100644 --- a/lib/helpers/document/compile.js +++ b/lib/helpers/document/compile.js @@ -46,7 +46,7 @@ function compile(tree, proto, prefix, options) { const limb = tree[key]; const hasSubprops = isPOJO(limb) && - Object.keys(limb).length > 0 && + utils.hasOwnKeys(limb) && (!limb[typeKey] || (typeKey === 'type' && isPOJO(limb.type) && limb.type.type)); const subprops = hasSubprops ? limb : null; @@ -132,7 +132,7 @@ function defineKey({ prop, subprops, prototype, prefix, options }) { writable: false, value: function() { return _this.get(path, null, { - virtuals: this && this.schema && this.schema.options && this.schema.options.toObject && this.schema.options.toObject.virtuals || null + virtuals: this?.schema?.options?.toObject?.virtuals || null }); } }); @@ -143,7 +143,7 @@ function defineKey({ prop, subprops, prototype, prefix, options }) { writable: false, value: function() { return _this.get(path, null, { - virtuals: this && this.schema && this.schema.options && this.schema.options.toJSON && this.schema.options.toJSON.virtuals || null + virtuals: this?.schema?.options?.toJSON?.virtuals || null }); } }); diff --git a/lib/helpers/populate/getModelsMapForPopulate.js b/lib/helpers/populate/getModelsMapForPopulate.js index 7d1b3f47fde..56a4d690d7b 100644 --- a/lib/helpers/populate/getModelsMapForPopulate.js +++ b/lib/helpers/populate/getModelsMapForPopulate.js @@ -26,6 +26,7 @@ module.exports = function getModelsMapForPopulate(model, docs, options) { let schema; let refPath; let modelNames; + let modelNamesSet; const available = {}; const modelSchema = model.schema; @@ -123,8 +124,10 @@ module.exports = function getModelsMapForPopulate(model, docs, options) { continue; } modelNames = modelNames || []; + modelNamesSet = modelNamesSet || new Set(); for (const modelName of _modelNames) { - if (modelNames.indexOf(modelName) === -1) { + if (modelNamesSet.has(modelName) === false) { + modelNamesSet.add(modelName); modelNames.push(modelName); } } @@ -225,8 +228,8 @@ module.exports = function getModelsMapForPopulate(model, docs, options) { schema = schema.$__schemaType; } - const ref = schema && schema.options && schema.options.ref; - refPath = schema && schema.options && schema.options.refPath; + const ref = schema?.options?.ref; + refPath = schema?.options?.refPath; if (schema != null && schema[schemaMixedSymbol] && !ref && @@ -721,7 +724,7 @@ function _findRefPathForDiscriminators(doc, modelSchema, data, options, normaliz if (schematype != null && schematype.$isMongooseDocumentArray && schematype.Constructor.discriminators != null && - Object.keys(schematype.Constructor.discriminators).length !== 0) { + utils.hasOwnKeys(schematype.Constructor.discriminators)) { const subdocs = utils.getValue(cur, doc); const remnant = options.path.substring(cur.length + 1); const discriminatorKey = schematype.Constructor.schema.options.discriminatorKey; diff --git a/lib/helpers/query/castUpdate.js b/lib/helpers/query/castUpdate.js index bc8bdf627bb..75b22b6f2ff 100644 --- a/lib/helpers/query/castUpdate.js +++ b/lib/helpers/query/castUpdate.js @@ -153,9 +153,9 @@ module.exports = function castUpdate(schema, obj, options, context, filter) { } } - if (Object.keys(ret).length === 0 && + if (utils.hasOwnKeys(ret) === false && options.upsert && - Object.keys(filter).length > 0) { + utils.hasOwnKeys(filter)) { // Trick the driver into allowing empty upserts to work around // https://github.com/mongodb/node-mongodb-native/pull/2490 // Shallow clone to avoid passing defaults in re: gh-13962 @@ -376,7 +376,7 @@ function walkUpdatePath(schema, obj, op, options, context, filter, prefix) { // we should be able to set a schema-less field // to an empty object literal hasKeys |= walkUpdatePath(schema, val, op, options, context, filter, prefix + key) || - (utils.isObject(val) && Object.keys(val).length === 0); + (utils.isObject(val) && utils.hasOwnKeys(val) === false); } } else { const isModifier = (key === '$each' || key === '$or' || key === '$and' || key === '$in'); diff --git a/lib/helpers/schema/applyReadConcern.js b/lib/helpers/schema/applyReadConcern.js index 050fa9c6df0..1367368abdd 100644 --- a/lib/helpers/schema/applyReadConcern.js +++ b/lib/helpers/schema/applyReadConcern.js @@ -9,7 +9,7 @@ module.exports = function applyReadConcern(schema, options) { // because you shouldn't set read concern on individual operations // within a transaction. // See: https://www.mongodb.com/docs/manual/reference/read-concern/ - if (options && options.session && options.session.transaction) { + if (options?.session?.transaction) { return; } diff --git a/lib/helpers/schema/applyWriteConcern.js b/lib/helpers/schema/applyWriteConcern.js index 28338cf58c3..2b7413713a3 100644 --- a/lib/helpers/schema/applyWriteConcern.js +++ b/lib/helpers/schema/applyWriteConcern.js @@ -1,5 +1,7 @@ 'use strict'; +const utils = require('../../utils'); + module.exports = function applyWriteConcern(schema, options) { if (options.writeConcern != null) { return; @@ -7,11 +9,11 @@ module.exports = function applyWriteConcern(schema, options) { // Don't apply default write concern to operations in transactions, // because setting write concern on an operation in a transaction is an error // See: https://www.mongodb.com/docs/manual/reference/write-concern/ - if (options && options.session && options.session.transaction) { + if (options?.session?.transaction) { return; } const writeConcern = schema.options.writeConcern ?? {}; - if (Object.keys(writeConcern).length != 0) { + if (utils.hasOwnKeys(writeConcern)) { options.writeConcern = {}; if (!('w' in options) && writeConcern.w != null) { options.writeConcern.w = writeConcern.w; diff --git a/lib/helpers/schema/getIndexes.js b/lib/helpers/schema/getIndexes.js index 362b0785260..80e3a96db00 100644 --- a/lib/helpers/schema/getIndexes.js +++ b/lib/helpers/schema/getIndexes.js @@ -69,7 +69,7 @@ module.exports = function getIndexes(schema) { const index = path._index || (path.embeddedSchemaType && path.embeddedSchemaType._index); - if (index !== false && index !== null && index !== undefined) { + if (index !== false && index != null) { const field = {}; const isObject = helperIsObject(index); const options = isObject ? { ...index } : {}; diff --git a/lib/helpers/update/applyTimestampsToUpdate.js b/lib/helpers/update/applyTimestampsToUpdate.js index e8d3217fbb9..95f95c06641 100644 --- a/lib/helpers/update/applyTimestampsToUpdate.js +++ b/lib/helpers/update/applyTimestampsToUpdate.js @@ -5,6 +5,7 @@ */ const get = require('../get'); +const utils = require('../../utils'); module.exports = applyTimestampsToUpdate; @@ -110,7 +111,7 @@ function applyTimestampsToUpdate(now, createdAt, updatedAt, currentUpdate, optio } } - if (Object.keys(updates.$set).length === 0) { + if (utils.hasOwnKeys(updates.$set) === false) { delete updates.$set; } return updates; diff --git a/lib/helpers/update/castArrayFilters.js b/lib/helpers/update/castArrayFilters.js index ed59af49b7e..405c4c06704 100644 --- a/lib/helpers/update/castArrayFilters.js +++ b/lib/helpers/update/castArrayFilters.js @@ -4,6 +4,7 @@ const castFilterPath = require('../query/castFilterPath'); const cleanPositionalOperators = require('../schema/cleanPositionalOperators'); const getPath = require('../schema/getPath'); const updatedPathsByArrayFilter = require('./updatedPathsByArrayFilter'); +const utils = require('../../utils'); module.exports = function castArrayFilters(query) { const arrayFilters = query.options.arrayFilters; @@ -74,7 +75,7 @@ function _castArrayFilters(arrayFilters, schema, strictQuery, updatedPathsByFilt if (updatedPathsByFilter[key] === null) { continue; } - if (Object.keys(updatedPathsByFilter).length === 0) { + if (utils.hasOwnKeys(updatedPathsByFilter) === false) { continue; } const dot = key.indexOf('.'); diff --git a/lib/model.js b/lib/model.js index 74d990b68c4..2f7935cd173 100644 --- a/lib/model.js +++ b/lib/model.js @@ -434,7 +434,7 @@ Model.prototype.$__save = async function $__save(options) { continue; } minimize(updateOp[key]); - if (Object.keys(updateOp[key]).length === 0) { + if (utils.hasOwnKeys(updateOp[key]) === false) { delete updateOp[key]; update.$unset = update.$unset || {}; update.$unset[key] = 1; @@ -1146,25 +1146,16 @@ Model.createCollection = async function createCollection(options) { throw err; }); - const collectionOptions = this && - this.schema && - this.schema.options && - this.schema.options.collectionOptions; + const collectionOptions = this?.schema?.options?.collectionOptions; if (collectionOptions != null) { options = Object.assign({}, collectionOptions, options); } - const schemaCollation = this && - this.schema && - this.schema.options && - this.schema.options.collation; + const schemaCollation = this?.schema?.options?.collation; if (schemaCollation != null) { options = Object.assign({ collation: schemaCollation }, options); } - const capped = this && - this.schema && - this.schema.options && - this.schema.options.capped; + const capped = this?.schema?.options?.capped; if (capped != null) { if (typeof capped === 'number') { options = Object.assign({ capped: true, size: capped }, options); @@ -1172,10 +1163,7 @@ Model.createCollection = async function createCollection(options) { options = Object.assign({ capped: true }, capped, options); } } - const timeseries = this && - this.schema && - this.schema.options && - this.schema.options.timeseries; + const timeseries = this?.schema?.options?.timeseries; if (timeseries != null) { options = Object.assign({ timeseries }, options); if (options.expireAfterSeconds != null) { @@ -1190,10 +1178,7 @@ Model.createCollection = async function createCollection(options) { } } - const clusteredIndex = this && - this.schema && - this.schema.options && - this.schema.options.clusteredIndex; + const clusteredIndex = this?.schema?.options?.clusteredIndex; if (clusteredIndex != null) { options = Object.assign({ clusteredIndex: { ...clusteredIndex, unique: true } }, options); } @@ -3977,7 +3962,7 @@ Model.updateOne = function updateOne(conditions, doc, options) { Model.replaceOne = function replaceOne(conditions, doc, options) { _checkContext(this, 'replaceOne'); - const versionKey = this && this.schema && this.schema.options && this.schema.options.versionKey || null; + const versionKey = this?.schema?.options?.versionKey || null; if (versionKey && !doc[versionKey]) { doc[versionKey] = 0; } @@ -4003,10 +3988,7 @@ function _update(model, op, conditions, doc, options) { } options = typeof options === 'function' ? options : clone(options); - const versionKey = model && - model.schema && - model.schema.options && - model.schema.options.versionKey || null; + const versionKey = model?.schema?.options?.versionKey || null; decorateUpdateWithVersionKey(doc, options, versionKey); return mq[op](conditions, doc, options); @@ -4986,7 +4968,7 @@ Model._applyQueryMiddleware = function _applyQueryMiddleware() { return !!contexts.query; } if (hook.name === 'deleteOne' || hook.name === 'updateOne') { - return !!contexts.query || Object.keys(contexts).length === 0; + return !!contexts.query || utils.hasOwnKeys(contexts) === false; } if (hook.query != null || hook.document != null) { return !!hook.query; diff --git a/lib/mongoose.js b/lib/mongoose.js index 864b686dd55..c20ae51ffb4 100644 --- a/lib/mongoose.js +++ b/lib/mongoose.js @@ -309,7 +309,7 @@ Mongoose.prototype.set = function getsetOptions(key, value) { if (optionValue && !_mongoose.connection) { _createDefaultConnection(_mongoose); } else if (optionValue === false && _mongoose.connection && _mongoose.connection[defaultConnectionSymbol]) { - if (_mongoose.connection.readyState === STATES.disconnected && Object.keys(_mongoose.connection.models).length === 0) { + if (_mongoose.connection.readyState === STATES.disconnected && utils.hasOwnKeys(_mongoose.connection.models) === false) { _mongoose.connections.shift(); } } @@ -605,8 +605,7 @@ Mongoose.prototype.model = function model(name, schema, collection, options) { _mongoose.options.overwriteModels : options.overwriteModels; if (_mongoose.models.hasOwnProperty(name) && options.cache !== false && overwriteModels !== true) { - if (originalSchema && - originalSchema.instanceOfSchema && + if (originalSchema?.instanceOfSchema && originalSchema !== _mongoose.models[name].schema) { throw new _mongoose.Error.OverwriteModelError(name); } diff --git a/lib/plugins/trackTransaction.js b/lib/plugins/trackTransaction.js index 857caac6044..1a45b572b1a 100644 --- a/lib/plugins/trackTransaction.js +++ b/lib/plugins/trackTransaction.js @@ -44,7 +44,7 @@ function _getAtomics(doc, previous) { utils.isMongooseDocumentArray(val) && val.length && val[arrayAtomicsSymbol] != null && - Object.keys(val[arrayAtomicsSymbol]).length !== 0) { + utils.hasOwnKeys(val[arrayAtomicsSymbol])) { const existing = previous.get(path) || {}; pathToAtomics.set(path, mergeAtomics(existing, val[arrayAtomicsSymbol])); } @@ -55,7 +55,7 @@ function _getAtomics(doc, previous) { const path = dirt.path; const val = dirt.value; - if (val != null && val[arrayAtomicsSymbol] != null && Object.keys(val[arrayAtomicsSymbol]).length !== 0) { + if (val != null && val[arrayAtomicsSymbol] != null && utils.hasOwnKeys(val[arrayAtomicsSymbol])) { const existing = previous.get(path) || {}; pathToAtomics.set(path, mergeAtomics(existing, val[arrayAtomicsSymbol])); } diff --git a/lib/query.js b/lib/query.js index 57bc291340f..9910a8c16cc 100644 --- a/lib/query.js +++ b/lib/query.js @@ -156,10 +156,7 @@ function Query(conditions, options, model, collection) { // versions of MongoDB this.$useProjection = true; - const collation = this && - this.schema && - this.schema.options && - this.schema.options.collation || null; + const collation = this?.schema?.options?.collation || null; if (collation != null) { this.options.collation = collation; } @@ -169,7 +166,7 @@ function Query(conditions, options, model, collection) { function isEmptyFilter(obj) { if (obj == null) return true; if (typeof obj !== 'object') return true; - if (Object.keys(obj).length === 0) return true; + if (utils.hasOwnKeys(obj) === false) return true; // Check $and, $or, $nor arrays for (const key of ['$and', '$or', '$nor']) { @@ -457,8 +454,8 @@ Query.prototype.clone = function() { * @api public */ -Query.prototype.slice = function() { - if (arguments.length === 0) { +Query.prototype.slice = function(...args) { + if (args.length === 0) { return this; } @@ -467,8 +464,8 @@ Query.prototype.slice = function() { let path; let val; - if (arguments.length === 1) { - const arg = arguments[0]; + if (args.length === 1) { + const arg = args[0]; if (typeof arg === 'object' && !Array.isArray(arg)) { const keys = Object.keys(arg); const numKeys = keys.length; @@ -479,19 +476,19 @@ Query.prototype.slice = function() { } this._ensurePath('slice'); path = this._path; - val = arguments[0]; - } else if (arguments.length === 2) { - if ('number' === typeof arguments[0]) { + val = args[0]; + } else if (args.length === 2) { + if ('number' === typeof args[0]) { this._ensurePath('slice'); path = this._path; - val = [arguments[0], arguments[1]]; + val = [args[0], args[1]]; } else { - path = arguments[0]; - val = arguments[1]; + path = args[0]; + val = args[1]; } - } else if (arguments.length === 3) { - path = arguments[0]; - val = [arguments[1], arguments[2]]; + } else if (args.length === 3) { + path = args[0]; + val = [args[1], args[2]]; } const p = {}; @@ -2006,7 +2003,7 @@ Query.prototype._fieldsForExec = function() { if (this._fields == null) { return null; } - if (Object.keys(this._fields).length === 0) { + if (utils.hasOwnKeys(this._fields) === false) { return null; } return clone(this._fields); @@ -2104,10 +2101,7 @@ Query.prototype._optionsForExec = function(model) { options.session = asyncLocalStorage.session; } - const readPreference = model && - model.schema && - model.schema.options && - model.schema.options.read; + const readPreference = model?.schema?.options?.read; if (!('readPreference' in options) && readPreference) { options.readPreference = readPreference; } @@ -2413,13 +2407,15 @@ Query.prototype._find = async function _find() { const mongooseOptions = this._mongooseOptions; const userProvidedFields = this._userProvidedFields || {}; - applyGlobalMaxTimeMS(this.options, this.model.db.options, this.model.base.options); - applyGlobalDiskUse(this.options, this.model.db.options, this.model.base.options); + const dbOptions = this.model.db.options; + const baseOptions = this.model.base.options; + applyGlobalMaxTimeMS(this.options, dbOptions, baseOptions); + applyGlobalDiskUse(this.options, dbOptions, baseOptions); // Separate options to pass down to `completeMany()` in case we need to // set a session on the document const completeManyOptions = { - session: this && this.options && this.options.session || null, + session: this?.options?.session || null, lean: mongooseOptions.lean || null }; @@ -2712,8 +2708,10 @@ Query.prototype._findOne = async function _findOne() { throw err; } - applyGlobalMaxTimeMS(this.options, this.model.db.options, this.model.base.options); - applyGlobalDiskUse(this.options, this.model.db.options, this.model.base.options); + const dbOptions = this.model.db.options; + const baseOptions = this.model.base.options; + applyGlobalMaxTimeMS(this.options, dbOptions, baseOptions); + applyGlobalDiskUse(this.options, dbOptions, baseOptions); const options = this._optionsForExec(); @@ -2808,8 +2806,10 @@ Query.prototype._countDocuments = async function _countDocuments() { throw this.error(); } - applyGlobalMaxTimeMS(this.options, this.model.db.options, this.model.base.options); - applyGlobalDiskUse(this.options, this.model.db.options, this.model.base.options); + const dbOptions = this.model.db.options; + const baseOptions = this.model.base.options; + applyGlobalMaxTimeMS(this.options, dbOptions, baseOptions); + applyGlobalDiskUse(this.options, dbOptions, baseOptions); const options = this._optionsForExec(); @@ -2836,7 +2836,7 @@ Query.prototype._applyTranslateAliases = function _applyTranslateAliases() { return; } - if (this.model?.schema?.aliases && Object.keys(this.model.schema.aliases).length > 0) { + if (this.model?.schema?.aliases && utils.hasOwnKeys(this.model.schema.aliases)) { this.model.translateAliases(this._conditions, true); this.model.translateAliases(this._fields, true); this.model.translateAliases(this._update, true); @@ -2974,8 +2974,10 @@ Query.prototype.__distinct = async function __distinct() { throw this.error(); } - applyGlobalMaxTimeMS(this.options, this.model.db.options, this.model.base.options); - applyGlobalDiskUse(this.options, this.model.db.options, this.model.base.options); + const dbOptions = this.model.db.options; + const baseOptions = this.model.base.options; + applyGlobalMaxTimeMS(this.options, dbOptions, baseOptions); + applyGlobalDiskUse(this.options, dbOptions, baseOptions); const options = this._optionsForExec(); @@ -3492,8 +3494,10 @@ Query.prototype._findOneAndUpdate = async function _findOneAndUpdate() { throw this.error(); } - applyGlobalMaxTimeMS(this.options, this.model.db.options, this.model.base.options); - applyGlobalDiskUse(this.options, this.model.db.options, this.model.base.options); + const dbOptions = this.model.db.options; + const baseOptions = this.model.base.options; + applyGlobalMaxTimeMS(this.options, dbOptions, baseOptions); + applyGlobalDiskUse(this.options, dbOptions, baseOptions); if ('strict' in this.options) { this._mongooseOptions.strict = this.options.strict; @@ -3509,7 +3513,7 @@ Query.prototype._findOneAndUpdate = async function _findOneAndUpdate() { this._update = setDefaultsOnInsert(this._conditions, this.model.schema, this._update, _opts); - if (!this._update || Object.keys(this._update).length === 0) { + if (!this._update || utils.hasOwnKeys(this._update) === false) { if (options.upsert) { // still need to do the upsert to empty doc const $set = clone(this._update); @@ -3526,7 +3530,7 @@ Query.prototype._findOneAndUpdate = async function _findOneAndUpdate() { // In order to make MongoDB 2.6 happy (see // https://jira.mongodb.org/browse/SERVER-12266 and related issues) // if we have an actual update document but $set is empty, junk the $set. - if (this._update.$set && Object.keys(this._update.$set).length === 0) { + if (this._update.$set && utils.hasOwnKeys(this._update.$set) === false) { delete this._update.$set; } } @@ -4015,7 +4019,7 @@ Query.prototype._mergeUpdate = function(update) { this._update = Array.isArray(update) ? [] : {}; } - if (update == null || (typeof update === 'object' && Object.keys(update).length === 0)) { + if (update == null || (typeof update === 'object' && utils.hasOwnKeys(update) === false)) { return; } @@ -4067,7 +4071,7 @@ async function _updateThunk(op) { } else { this._update = this._castUpdate(this._update); - if (this._update == null || Object.keys(this._update).length === 0) { + if (this._update == null || utils.hasOwnKeys(this._update) === false) { return { acknowledged: false }; } @@ -4888,8 +4892,7 @@ Query.prototype._castUpdate = function _castUpdate(obj) { * @api public */ -Query.prototype.populate = function() { - const args = Array.from(arguments); +Query.prototype.populate = function(...args) { // Bail when given no truthy arguments if (!args.some(Boolean)) { return this; @@ -4902,7 +4905,7 @@ Query.prototype.populate = function() { if (opts.lean != null) { const lean = opts.lean; for (const populateOptions of res) { - if ((populateOptions && populateOptions.options && populateOptions.options.lean) == null) { + if (populateOptions?.options?.lean == null) { populateOptions.options = populateOptions.options || {}; populateOptions.options.lean = lean; } diff --git a/lib/queryHelpers.js b/lib/queryHelpers.js index 9cb2f546756..4681023f496 100644 --- a/lib/queryHelpers.js +++ b/lib/queryHelpers.js @@ -30,11 +30,11 @@ exports.preparePopulationOptionsMQ = function preparePopulationOptionsMQ(query, // lean options should trickle through all queries if (options.lean != null) { pop - .filter(p => (p && p.options && p.options.lean) == null) + .filter(p => p?.options?.lean == null) .forEach(makeLean(options.lean)); } - const session = query && query.options && query.options.session || null; + const session = query?.options?.session || null; if (session != null) { pop.forEach(path => { if (path.options == null) { @@ -204,10 +204,7 @@ exports.applyPaths = function applyPaths(fields, schema, sanitizeProjection) { } break; case false: - if (schema && - schema.paths['_id'] && - schema.paths['_id'].options && - schema.paths['_id'].options.select === false) { + if (schema?.paths['_id']?.options?.select === false) { fields._id = 0; } diff --git a/lib/schema.js b/lib/schema.js index d62fe80cdb6..8a4ddddf64c 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -722,9 +722,7 @@ Schema.prototype._defaultToObjectOptions = function(json) { return this._defaultToObjectOptionsMap[path]; } - const baseOptions = this.base && - this.base.options && - this.base.options[path] || {}; + const baseOptions = this.base?.options?.[path] || {}; const schemaOptions = this.options[path] || {}; // merge base default options with Schema's set default options if available. // `clone` is necessary here because `utils.options` directly modifies the second input. @@ -847,7 +845,7 @@ Schema.prototype.add = function add(obj, prefix) { schemaType.discriminator(key, val[0].discriminators[key]); } } - } else if (Object.keys(val).length < 1) { + } else if (utils.hasOwnKeys(val) === false) { // Special-case: {} always interpreted as Mixed path so leaf at this node if (prefix) { this.nested[prefix.substring(0, prefix.length - 1)] = true; @@ -862,7 +860,7 @@ Schema.prototype.add = function add(obj, prefix) { } else { // There IS a bona-fide type key that may also be a POJO const _typeDef = val[typeKey]; - if (isPOJO(_typeDef) && Object.keys(_typeDef).length > 0) { + if (isPOJO(_typeDef) && utils.hasOwnKeys(_typeDef)) { // If a POJO is the value of a type key, make it a subdocument if (prefix) { this.nested[prefix.substring(0, prefix.length - 1)] = true; @@ -965,7 +963,7 @@ Schema.prototype._removeEncryptedField = function _removeEncryptedField(path) { * @returns {boolean} */ Schema.prototype._hasEncryptedFields = function _hasEncryptedFields() { - return Object.keys(this.encryptedFields).length > 0; + return utils.hasOwnKeys(this.encryptedFields); }; /** @@ -1674,7 +1672,7 @@ Schema.prototype.interpretAsType = function(path, obj, options) { if (typeof cast === 'string') { cast = MongooseTypes[cast.charAt(0).toUpperCase() + cast.substring(1)]; } else if (utils.isPOJO(castFromTypeKey)) { - if (Object.keys(castFromTypeKey).length) { + if (utils.hasOwnKeys(castFromTypeKey)) { // The `minimize` and `typeKey` options propagate to child schemas // declared inline, like `{ arr: [{ val: { $type: String } }] }`. // See gh-3560 diff --git a/lib/schema/array.js b/lib/schema/array.js index 14b60f22ea2..945cc1a90eb 100644 --- a/lib/schema/array.js +++ b/lib/schema/array.js @@ -255,9 +255,7 @@ SchemaArray.prototype.checkRequired = function checkRequired(value, doc) { SchemaArray.prototype.enum = function() { let arr = this; while (true) { - const instance = arr && - arr.embeddedSchemaType && - arr.embeddedSchemaType.instance; + const instance = arr?.embeddedSchemaType?.instance; if (instance === 'Array') { arr = arr.embeddedSchemaType; continue; diff --git a/lib/schema/map.js b/lib/schema/map.js index 2ac2a3371a8..e7bf1363f87 100644 --- a/lib/schema/map.js +++ b/lib/schema/map.js @@ -172,7 +172,7 @@ SchemaMap.prototype._createNestedSchemaType = function _createNestedSchemaType(s let _mapType = { type: {} }; if (utils.hasUserDefinedProperty(obj, 'of')) { const isInlineSchema = utils.isPOJO(obj.of) && - Object.keys(obj.of).length > 0 && + utils.hasOwnKeys(obj.of) && !utils.hasUserDefinedProperty(obj.of, schema.options.typeKey); if (isInlineSchema) { _mapType = { [schema.options.typeKey]: new Schema(obj.of) }; diff --git a/lib/schema/mixed.js b/lib/schema/mixed.js index 93740640891..c95a373a9e9 100644 --- a/lib/schema/mixed.js +++ b/lib/schema/mixed.js @@ -26,7 +26,7 @@ function SchemaMixed(path, options, _schemaOptions, parentSchema) { if (Array.isArray(def) && def.length === 0) { // make sure empty array defaults are handled options.default = Array; - } else if (!options.shared && isObject(def) && Object.keys(def).length === 0) { + } else if (!options.shared && isObject(def) && utils.hasOwnKeys(def) === false) { // prevent odd "shared" objects between documents options.default = function() { return {}; diff --git a/lib/schema/number.js b/lib/schema/number.js index 33d9615fb9f..5e172639380 100644 --- a/lib/schema/number.js +++ b/lib/schema/number.js @@ -221,7 +221,7 @@ SchemaNumber.prototype.min = function(value, message) { }, this); } - if (value !== null && value !== undefined) { + if (value != null) { let msg = message || MongooseError.messages.Number.min; msg = msg.replace(/{MIN}/, value); this.validators.push({ @@ -275,7 +275,7 @@ SchemaNumber.prototype.max = function(value, message) { }, this); } - if (value !== null && value !== undefined) { + if (value != null) { let msg = message || MongooseError.messages.Number.max; msg = msg.replace(/{MAX}/, value); this.validators.push({ diff --git a/lib/schema/string.js b/lib/schema/string.js index fbc88b38c02..7197bc0e103 100644 --- a/lib/schema/string.js +++ b/lib/schema/string.js @@ -416,7 +416,7 @@ SchemaString.prototype.minlength = function(value, message) { }, this); } - if (value !== null && value !== undefined) { + if (value != null) { let msg = message || MongooseError.messages.String.minlength; msg = msg.replace(/{MINLENGTH}/, value); this.validators.push({ @@ -472,7 +472,7 @@ SchemaString.prototype.maxlength = function(value, message) { }, this); } - if (value !== null && value !== undefined) { + if (value != null) { let msg = message || MongooseError.messages.String.maxlength; msg = msg.replace(/{MAXLENGTH}/, value); this.validators.push({ diff --git a/lib/schema/subdocument.js b/lib/schema/subdocument.js index dcc42327e5b..64975f724d6 100644 --- a/lib/schema/subdocument.js +++ b/lib/schema/subdocument.js @@ -41,8 +41,7 @@ function SchemaSubdocument(schema, path, options, parentSchema) { if (schema.options.timeseries) { throw new InvalidSchemaOptionError(path, 'timeseries'); } - const schemaTypeIdOption = SchemaSubdocument.defaultOptions && - SchemaSubdocument.defaultOptions._id; + const schemaTypeIdOption = SchemaSubdocument.defaultOptions?._id; if (schemaTypeIdOption != null) { options = options || {}; options._id = schemaTypeIdOption; @@ -216,7 +215,7 @@ SchemaSubdocument.prototype.cast = function(val, doc, init, priorVal, options) { applyDefaults(subdoc, selected, exclude); } else { options = Object.assign({}, options, { priorDoc: priorVal }); - if (Object.keys(val).length === 0) { + if (utils.hasOwnKeys(val) === false) { return new Constructor({}, selected, doc, options); } diff --git a/lib/schemaType.js b/lib/schemaType.js index 063c5b73047..a30f90cdeca 100644 --- a/lib/schemaType.js +++ b/lib/schemaType.js @@ -1209,7 +1209,7 @@ SchemaType.prototype.getDefault = function(scope, init, options) { ret = this.defaultValue; } - if (ret !== null && ret !== undefined) { + if (ret != null) { if (typeof ret === 'object' && (!this.options || !this.options.shared)) { ret = clone(ret); } diff --git a/lib/types/array/index.js b/lib/types/array/index.js index b21c23bfd12..19f37cfd039 100644 --- a/lib/types/array/index.js +++ b/lib/types/array/index.js @@ -85,7 +85,7 @@ function MongooseArray(values, path, doc, schematype) { if (mongooseArrayMethods.hasOwnProperty(prop)) { return mongooseArrayMethods[prop]; } - if (schematype && schematype.virtuals && schematype.virtuals.hasOwnProperty(prop)) { + if (schematype?.virtuals?.hasOwnProperty(prop)) { return schematype.virtuals[prop].applyGetters(undefined, target); } if (typeof prop === 'string' && numberRE.test(prop) && schematype?.embeddedSchemaType != null) { @@ -99,7 +99,7 @@ function MongooseArray(values, path, doc, schematype) { mongooseArrayMethods.set.call(proxy, prop, value, false); } else if (internals.hasOwnProperty(prop)) { internals[prop] = value; - } else if (schematype && schematype.virtuals && schematype.virtuals.hasOwnProperty(prop)) { + } else if (schematype?.virtuals?.hasOwnProperty(prop)) { schematype.virtuals[prop].applySetters(value, target); } else { __array[prop] = value; diff --git a/lib/types/array/methods/index.js b/lib/types/array/methods/index.js index 46fe80815b8..a14cf16fcf3 100644 --- a/lib/types/array/methods/index.js +++ b/lib/types/array/methods/index.js @@ -338,7 +338,7 @@ const methods = { // check for impossible $atomic combos (Mongo denies more than one // $atomic op on a single path - if (atomics.$set || Object.keys(atomics).length && !(op in atomics)) { + if (atomics.$set || utils.hasOwnKeys(atomics) && !(op in atomics)) { // a different op was previously registered. // save the entire thing. this[arrayAtomicsSymbol] = { $set: this }; @@ -995,7 +995,7 @@ function _minimizePath(obj, parts, i) { } _minimizePath(obj[parts[0]], parts, i + 1); - if (obj[parts[0]] != null && typeof obj[parts[0]] === 'object' && Object.keys(obj[parts[0]]).length === 0) { + if (obj[parts[0]] != null && typeof obj[parts[0]] === 'object' && utils.hasOwnKeys(obj[parts[0]]) === false) { delete obj[parts[0]]; } } diff --git a/lib/types/documentArray/index.js b/lib/types/documentArray/index.js index ccc0d230fdb..0fd138757db 100644 --- a/lib/types/documentArray/index.js +++ b/lib/types/documentArray/index.js @@ -79,7 +79,7 @@ function MongooseDocumentArray(values, path, doc, schematype) { if (DocumentArrayMethods.hasOwnProperty(prop)) { return DocumentArrayMethods[prop]; } - if (schematype && schematype.virtuals && schematype.virtuals.hasOwnProperty(prop)) { + if (schematype?.virtuals?.hasOwnProperty(prop)) { return schematype.virtuals[prop].applyGetters(undefined, target); } if (ArrayMethods.hasOwnProperty(prop)) { @@ -93,7 +93,7 @@ function MongooseDocumentArray(values, path, doc, schematype) { DocumentArrayMethods.set.call(proxy, prop, value, false); } else if (internals.hasOwnProperty(prop)) { internals[prop] = value; - } else if (schematype && schematype.virtuals && schematype.virtuals.hasOwnProperty(prop)) { + } else if (schematype?.virtuals?.hasOwnProperty(prop)) { schematype.virtuals[prop].applySetters(value, target); } else { __array[prop] = value; diff --git a/lib/types/documentArray/methods/index.js b/lib/types/documentArray/methods/index.js index b17a31d0638..435725a95c6 100644 --- a/lib/types/documentArray/methods/index.js +++ b/lib/types/documentArray/methods/index.js @@ -65,7 +65,7 @@ const methods = { value instanceof Constructor; if (isInstance || // Hack re: #5001, see #5005 - (value && value.constructor && value.constructor.baseCasterConstructor === Constructor)) { + value?.constructor?.baseCasterConstructor === Constructor) { if (!(value[documentArrayParent] && value.__parentArray)) { // value may have been created using array.create() value[documentArrayParent] = this[arrayParentSymbol]; @@ -133,9 +133,9 @@ const methods = { const schemaType = this[arraySchemaSymbol]; let idSchemaType = null; - if (schemaType && schemaType.schema) { + if (schemaType?.schema) { idSchemaType = schemaType.schema.path('_id'); - } else if (schemaType && schemaType.casterConstructor && schemaType.casterConstructor.schema) { + } else if (schemaType?.casterConstructor?.schema) { idSchemaType = schemaType.casterConstructor.schema.path('_id'); } diff --git a/lib/types/subdocument.js b/lib/types/subdocument.js index 5d39e1c214e..89d06fcaaf3 100644 --- a/lib/types/subdocument.js +++ b/lib/types/subdocument.js @@ -422,7 +422,7 @@ Subdocument.prototype.$toObject = function $toObject(options, json) { // If `$toObject()` was called recursively, respect the minimize option, including schematype level minimize. // If minimize is set, then we can minimize out the whole object. - if (Object.keys(ret).length === 0 && options?._calledWithOptions != null) { + if (utils.hasOwnKeys(ret) === false && options?._calledWithOptions != null) { const minimize = options._calledWithOptions?.minimize ?? this?.$__schemaTypeOptions?.minimize ?? options.minimize; if (minimize && !this.constructor.$__required) { return undefined; diff --git a/lib/utils.js b/lib/utils.js index 2b896dce28b..217c63906bc 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -436,10 +436,26 @@ exports.isNativeObject = function(arg) { * @param {Any} val */ -exports.isEmptyObject = function(val) { - return val != null && - typeof val === 'object' && - Object.keys(val).length === 0; +exports.isEmptyObject = function isEmptyObject(val) { + if (val == null || typeof val !== 'object') { + return false; + } + return exports.hasOwnKeys(val) === false; +}; + +/** + * Determines if `obj` has any own keys. Assumes obj is already an object. + * Faster than Object.keys(obj).length > 0. + * @param {Object} obj + */ + +exports.hasOwnKeys = function hasOwnKeys(obj) { + for (const key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + return true; + } + } + return false; }; /**