Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,22 @@ console.log(
// { key1: [], key2: [], nested: { key3: 'a', key4: [] } }
```

### `preserveNullishArrays`

Optional boolean.
If provided, null values in arrays will be preserved instead of being removed.

```js
import removeUndefinedObjects from 'remove-undefined-objects';

console.log(removeUndefinedObjects({ key1: [null, undefined], key2: 123, key3: null }));
// { key2: 123, key3: null }
console.log(
removeUndefinedObjects({ key1: [null, undefined], key2: 123, key3: null }, { preserveNullishArrays: true }),
);
// { key1: [null], key2: 123, key3: null }
```

### `removeAllFalsy`

Optional boolean.
Expand Down
43 changes: 36 additions & 7 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,38 @@ function isEmptyArray(arr: unknown) {

interface RemovalOptions {
preserveEmptyArray?: boolean;
preserveNullishArrays?: boolean;
removeAllFalsy?: boolean;
}

// Remove objects that has undefined value or recursively contain undefined values
// biome-ignore lint/suspicious/noExplicitAny: This method does its own type assertions.
function removeUndefined(obj: any): any {
if (obj === undefined) {
return undefined;
}
// Preserve null
if (obj === null) {
return null;
}
// Remove undefined in arrays
if (Array.isArray(obj)) {
return obj.map(removeUndefined).filter(item => item !== undefined);
}
if (typeof obj === 'object') {
// biome-ignore lint/suspicious/noExplicitAny: We're just passing around the object values
const cleaned: Record<string, any> = {};
Object.entries(obj).forEach(([key, value]) => {
const cleanedValue = removeUndefined(value);
if (cleanedValue !== undefined) {
cleaned[key] = cleanedValue;
}
});
return cleaned;
}
return obj;
}

// Modified from here: https://stackoverflow.com/a/43781499
// biome-ignore lint/suspicious/noExplicitAny: This method does its own type assertions.
function stripEmptyObjects(obj: any, options: RemovalOptions = {}) {
Expand Down Expand Up @@ -69,8 +98,8 @@ function stripEmptyObjects(obj: any, options: RemovalOptions = {}) {
} else {
cleanObj[idx] = value;
}
} else if (value === null) {
// Null entries within an array should be removed.
} else if (value === null && (options.removeAllFalsy || !options.preserveNullishArrays)) {
// Null entries within an array should be removed by default, unless explicitly preserved
delete cleanObj[idx];
}
});
Expand All @@ -85,12 +114,12 @@ export default function removeUndefinedObjects<T>(obj?: T, options?: RemovalOpti
return undefined;
}

// JSON.stringify removes undefined values. Though `[undefined]` will be converted with this to
// `[null]`, we'll clean that up next.
// eslint-disable-next-line try-catch-failsafe/json-parse
let withoutUndefined = JSON.parse(JSON.stringify(obj));
// If array nulls are preserved, use the custom removeUndefined function so that
// undefined values in arrays aren't converted to nulls, which stringify does
// If we're not preserving array nulls (default behavior), it doesn't matter that the undefined array values are converted to nulls
let withoutUndefined = options?.preserveNullishArrays ? removeUndefined(obj) : JSON.parse(JSON.stringify(obj));

// Then we recursively remove all empty objects and nullish arrays.
// Then we recursively remove all empty objects and nullish arrays
withoutUndefined = stripEmptyObjects(withoutUndefined, options);

// If the only thing that's leftover is an empty object or empty array then return nothing.
Expand Down
15 changes: 15 additions & 0 deletions test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,18 @@ test('should remove undefined and null values from arrays', () => {
'',
]);
});

test('should not remove null values from arrays when preserveArrayNulls is true', () => {
expect(removeUndefinedObjects([null], { preserveNullishArrays: true })).toStrictEqual([null]);
expect(removeUndefinedObjects([undefined], { preserveNullishArrays: true })).toBeUndefined();
expect(removeUndefinedObjects([null, undefined], { preserveNullishArrays: true })).toStrictEqual([null]);
expect(
removeUndefinedObjects([null, undefined, { a: null, b: undefined }], { preserveNullishArrays: true }),
).toStrictEqual([null, { a: null }]);
expect(
removeUndefinedObjects(
{ a: 'a', empty_nested: { nested2: { nested3: undefined } }, nested_array: { b: [null, 1, undefined, 2] } },
{ preserveNullishArrays: true },
),
).toStrictEqual({ a: 'a', nested_array: { b: [null, 1, 2] } });
});