diff --git a/README.md b/README.md index e8e2741..0729099 100644 --- a/README.md +++ b/README.md @@ -31,3 +31,17 @@ The following items will NOT be removed: * Empty string, `''` * Null, `null` + +## Options + +### `removeAllFalsy` + +Optional boolean. +If provided, the empty string `''` and `null` will be removed as well. + +```js +import removeUndefinedObjects from 'remove-undefined-objects'; + +console.log(removeUndefinedObjects({key1: null, key2: 123, key3: ''}), {removeAllFalsy: true}); +// { key2: 123 } +``` diff --git a/src/index.ts b/src/index.ts index 17db073..75e01e0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,30 +6,43 @@ function isEmptyObject(obj: unknown) { return typeof obj === 'object' && obj !== null && !Object.keys(obj).length; } +interface RemovalOptions { + removeAllFalsy?: boolean; +} + // Modified from here: https://stackoverflow.com/a/43781499 -function stripEmptyObjects(obj: any) { +function stripEmptyObjects(obj: any, options: RemovalOptions = {}) { const cleanObj = obj; + if (obj === null && options.removeAllFalsy) { + return undefined; + } + if (!isObject(obj) && !Array.isArray(cleanObj)) { return cleanObj; - } else if (obj === null) { - return undefined; } if (!Array.isArray(cleanObj)) { Object.keys(cleanObj).forEach(key => { let value = cleanObj[key]; - if (typeof value === 'object' && value !== null) { - value = stripEmptyObjects(value); + if (typeof value !== 'object') { + return; + } - if (isEmptyObject(value)) { + if (value === null) { + if (options.removeAllFalsy) { delete cleanObj[key]; - } else { - cleanObj[key] = value; } - } else if (value === null) { - // Null properties in an object should remain! + return; + } + + value = stripEmptyObjects(value, options); + + if (isEmptyObject(value)) { + delete cleanObj[key]; + } else { + cleanObj[key] = value; } }); @@ -39,7 +52,7 @@ function stripEmptyObjects(obj: any) { cleanObj.forEach((o, idx) => { let value = o; if (typeof value === 'object' && value !== null) { - value = stripEmptyObjects(value); + value = stripEmptyObjects(value, options); if (isEmptyObject(value)) { delete cleanObj[idx]; @@ -57,7 +70,7 @@ function stripEmptyObjects(obj: any) { return cleanObj.filter(el => el !== undefined); } -export default function removeUndefinedObjects(obj?: T): T | undefined { +export default function removeUndefinedObjects(obj?: T, options?: RemovalOptions): T | undefined { if (obj === undefined) { return undefined; } @@ -68,7 +81,7 @@ export default function removeUndefinedObjects(obj?: T): T | undefined { let withoutUndefined = JSON.parse(JSON.stringify(obj)); // Then we recursively remove all empty objects and nullish arrays. - withoutUndefined = stripEmptyObjects(withoutUndefined); + withoutUndefined = stripEmptyObjects(withoutUndefined, options); // If the only thing that's leftover is an empty object then return nothing. if (isEmptyObject(withoutUndefined)) return undefined; diff --git a/test/index.test.ts b/test/index.test.ts index 5fd9950..8e529d5 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -20,6 +20,20 @@ test('should leave primitives alone', () => { expect(removeUndefinedObjects(undefined)).toBeUndefined(); }); +test('should leave only truthy primitives alone when removeAllFalsy is true', () => { + expect(removeUndefinedObjects(1234, { removeAllFalsy: true })).toBe(1234); + expect(removeUndefinedObjects('1234', { removeAllFalsy: true })).toBe('1234'); + expect(removeUndefinedObjects(null, { removeAllFalsy: true })).toBeUndefined(); + expect(removeUndefinedObjects(undefined, { removeAllFalsy: true })).toBeUndefined(); +}); + +test("should also remove '' and null values when removeAllFalsy is true", () => { + expect(removeUndefinedObjects({ value: 1234 }, { removeAllFalsy: true })).toStrictEqual({ value: 1234 }); + expect(removeUndefinedObjects({ value: '1234' }, { removeAllFalsy: true })).toStrictEqual({ value: '1234' }); + expect(removeUndefinedObjects({ value: null }, { removeAllFalsy: true })).toBeUndefined(); + expect(removeUndefinedObjects({ value: undefined }, { removeAllFalsy: true })).toBeUndefined(); +}); + test('should remove empty objects with only empty properties', () => { const obj = { a: { @@ -66,6 +80,25 @@ test('should remove empty arrays from within object', () => { }); }); +test('should remove empty arrays and falsy values from within object when removeAllFalsy is true', () => { + const obj = { + a: { + b: undefined, + c: { + d: undefined, + }, + }, + d: [1234, undefined], + e: [], + f: null, + g: [null, undefined, null], + }; + + expect(removeUndefinedObjects(obj, { removeAllFalsy: true })).toStrictEqual({ + d: [1234], + }); +}); + test('should remove undefined and null values from arrays', () => { expect(removeUndefinedObjects([undefined, undefined])).toBeUndefined(); expect(removeUndefinedObjects([null])).toBeUndefined();