Skip to content

Commit 34f3cc2

Browse files
authored
refactor: retain null values in array (#149)
* refactor: use own function to remove undefined & stop removing nulls in arrays * feat: make remove array nulls optional * doc: add more example in readme * style: refactor naming & comments * docs: update readme
1 parent 6bc9175 commit 34f3cc2

File tree

3 files changed

+67
-7
lines changed

3 files changed

+67
-7
lines changed

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,22 @@ console.log(
5454
// { key1: [], key2: [], nested: { key3: 'a', key4: [] } }
5555
```
5656

57+
### `preserveNullishArrays`
58+
59+
Optional boolean.
60+
If provided, null values in arrays will be preserved instead of being removed.
61+
62+
```js
63+
import removeUndefinedObjects from 'remove-undefined-objects';
64+
65+
console.log(removeUndefinedObjects({ key1: [null, undefined], key2: 123, key3: null }));
66+
// { key2: 123, key3: null }
67+
console.log(
68+
removeUndefinedObjects({ key1: [null, undefined], key2: 123, key3: null }, { preserveNullishArrays: true }),
69+
);
70+
// { key1: [null], key2: 123, key3: null }
71+
```
72+
5773
### `removeAllFalsy`
5874

5975
Optional boolean.

src/index.ts

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,38 @@ function isEmptyArray(arr: unknown) {
1212

1313
interface RemovalOptions {
1414
preserveEmptyArray?: boolean;
15+
preserveNullishArrays?: boolean;
1516
removeAllFalsy?: boolean;
1617
}
1718

19+
// Remove objects that has undefined value or recursively contain undefined values
20+
// biome-ignore lint/suspicious/noExplicitAny: This method does its own type assertions.
21+
function removeUndefined(obj: any): any {
22+
if (obj === undefined) {
23+
return undefined;
24+
}
25+
// Preserve null
26+
if (obj === null) {
27+
return null;
28+
}
29+
// Remove undefined in arrays
30+
if (Array.isArray(obj)) {
31+
return obj.map(removeUndefined).filter(item => item !== undefined);
32+
}
33+
if (typeof obj === 'object') {
34+
// biome-ignore lint/suspicious/noExplicitAny: We're just passing around the object values
35+
const cleaned: Record<string, any> = {};
36+
Object.entries(obj).forEach(([key, value]) => {
37+
const cleanedValue = removeUndefined(value);
38+
if (cleanedValue !== undefined) {
39+
cleaned[key] = cleanedValue;
40+
}
41+
});
42+
return cleaned;
43+
}
44+
return obj;
45+
}
46+
1847
// Modified from here: https://stackoverflow.com/a/43781499
1948
// biome-ignore lint/suspicious/noExplicitAny: This method does its own type assertions.
2049
function stripEmptyObjects(obj: any, options: RemovalOptions = {}) {
@@ -69,8 +98,8 @@ function stripEmptyObjects(obj: any, options: RemovalOptions = {}) {
6998
} else {
7099
cleanObj[idx] = value;
71100
}
72-
} else if (value === null) {
73-
// Null entries within an array should be removed.
101+
} else if (value === null && (options.removeAllFalsy || !options.preserveNullishArrays)) {
102+
// Null entries within an array should be removed by default, unless explicitly preserved
74103
delete cleanObj[idx];
75104
}
76105
});
@@ -85,12 +114,12 @@ export default function removeUndefinedObjects<T>(obj?: T, options?: RemovalOpti
85114
return undefined;
86115
}
87116

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

93-
// Then we recursively remove all empty objects and nullish arrays.
122+
// Then we recursively remove all empty objects and nullish arrays
94123
withoutUndefined = stripEmptyObjects(withoutUndefined, options);
95124

96125
// If the only thing that's leftover is an empty object or empty array then return nothing.

test/index.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,3 +141,18 @@ test('should remove undefined and null values from arrays', () => {
141141
'',
142142
]);
143143
});
144+
145+
test('should not remove null values from arrays when preserveArrayNulls is true', () => {
146+
expect(removeUndefinedObjects([null], { preserveNullishArrays: true })).toStrictEqual([null]);
147+
expect(removeUndefinedObjects([undefined], { preserveNullishArrays: true })).toBeUndefined();
148+
expect(removeUndefinedObjects([null, undefined], { preserveNullishArrays: true })).toStrictEqual([null]);
149+
expect(
150+
removeUndefinedObjects([null, undefined, { a: null, b: undefined }], { preserveNullishArrays: true }),
151+
).toStrictEqual([null, { a: null }]);
152+
expect(
153+
removeUndefinedObjects(
154+
{ a: 'a', empty_nested: { nested2: { nested3: undefined } }, nested_array: { b: [null, 1, undefined, 2] } },
155+
{ preserveNullishArrays: true },
156+
),
157+
).toStrictEqual({ a: 'a', nested_array: { b: [null, 1, 2] } });
158+
});

0 commit comments

Comments
 (0)