Skip to content

Commit

Permalink
feat(mongoose): export omitUndefined() helper
Browse files Browse the repository at this point in the history
Fix #14569
  • Loading branch information
vkarpov15 committed May 9, 2024
1 parent 822392e commit 9e75b37
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 8 deletions.
20 changes: 20 additions & 0 deletions lib/helpers/omitUndefined.js
@@ -0,0 +1,20 @@
'use strict';

module.exports = function omitUndefined(val) {
if (val == null || typeof val !== 'object') {
return val;
}
if (Array.isArray(val)) {
for (let i = val.length - 1; i >= 0; --i) {
if (val[i] === undefined) {
val.splice(i, 1);
}
}
}
for (const key of Object.keys(val)) {
if (val[key] === void 0) {
delete val[key];
}
}
return val;
};
10 changes: 2 additions & 8 deletions lib/helpers/query/cast$expr.js
Expand Up @@ -3,6 +3,7 @@
const CastError = require('../../error/cast');
const StrictModeError = require('../../error/strict');
const castNumber = require('../../cast/number');
const omitUndefined = require('../omitUndefined');

const booleanComparison = new Set(['$and', '$or']);
const comparisonOperator = new Set(['$cmp', '$eq', '$lt', '$lte', '$gt', '$gte']);
Expand Down Expand Up @@ -125,18 +126,11 @@ function _castExpression(val, schema, strictQuery) {
val.$round = $round.map(v => castNumberOperator(v, schema, strictQuery));
}

_omitUndefined(val);
omitUndefined(val);

return val;
}

function _omitUndefined(val) {
const keys = Object.keys(val);
for (let i = 0, len = keys.length; i < len; ++i) {
(val[keys[i]] === void 0) && delete val[keys[i]];
}
}

// { $op: <number> }
function castNumberOperator(val) {
if (!isLiteral(val)) {
Expand Down
22 changes: 22 additions & 0 deletions lib/mongoose.js
Expand Up @@ -1281,6 +1281,28 @@ Mongoose.prototype.skipMiddlewareFunction = Kareem.skipWrappedFunction;

Mongoose.prototype.overwriteMiddlewareResult = Kareem.overwriteResult;

/**
* Takes in an object and deletes any keys from the object whose values
* are strictly equal to `undefined`.
* This function is useful for query filters because Mongoose treats
* `TestModel.find({ name: undefined })` as `TestModel.find({ name: null })`.
*
* #### Example:
*
* const filter = { name: 'John', age: undefined, status: 'active' };
* mongoose.omitUndefined(filter); // { name: 'John', status: 'active' }
* filter; // { name: 'John', status: 'active' }
*
* await UserModel.findOne(mongoose.omitUndefined(filter));
*
* @method omitUndefined
* @param {Object} [val] the object to remove undefined keys from
* @returns {Object} the object passed in
* @api public
*/

Mongoose.prototype.omitUndefined = require('./helpers/omitUndefined');

/**
* The exports object is an instance of Mongoose.
*
Expand Down
2 changes: 2 additions & 0 deletions test/types/base.test.ts
Expand Up @@ -69,3 +69,5 @@ function setAsObject() {

expectError(mongoose.set({ invalid: true }));
}

const x: { name: string } = mongoose.omitUndefined({ name: 'foo' });
2 changes: 2 additions & 0 deletions types/index.d.ts
Expand Up @@ -68,6 +68,8 @@ declare module 'mongoose' {
/** Gets mongoose options */
export function get<K extends keyof MongooseOptions>(key: K): MongooseOptions[K];

export function omitUndefined<T extends Record<string, any>>(val: T): T;

/* ! ignore */
export type CompileModelOptions = {
overwriteModels?: boolean,
Expand Down

0 comments on commit 9e75b37

Please sign in to comment.