compactDeep
Recursively removes values from arrays and objects based on a predicate.
1/**
2 * Recursively removes values from arrays and objects based on a predicate.
3 *
4 * Options:
5 * - predicate: Function used to test values (defaults to Boolean).
6 * - preserveEmptyContainers: If true, keeps empty arrays/objects instead of removing them.
7 * - fallback: A value to return instead of removing a value entirely.
8 *
9 * @param value - The input structure (object, array, or primitive).
10 * @param options - Optional config object.
11 * @returns Cleaned structure or fallback/undefined if removed.
12 */
13export function compactDeep<T = any>(
14 value: T,
15 options: {
16 predicate?: (val: any) => boolean;
17 preserveEmptyContainers?: boolean;
18 fallback?: any;
19 } = {}
20): any {
21 const {
22 predicate = Boolean,
23 preserveEmptyContainers = false,
24 fallback
25 } = options;
26
27 if (Array.isArray(value)) {
28 const cleaned = value
29 .map(item => compactDeep(item, options))
30 .filter(item => predicate(item));
31 if (cleaned.length > 0 || preserveEmptyContainers) return cleaned;
32 return fallback !== undefined ? fallback : undefined;
33 }
34
35 if (value && typeof value === 'object' && value.constructor === Object) {
36 const result: Record<string, any> = {};
37 for (const [key, val] of Object.entries(value)) {
38 const cleaned = compactDeep(val, options);
39 if (predicate(cleaned)) result[key] = cleaned;
40 }
41 if (Object.keys(result).length > 0 || preserveEmptyContainers) return result;
42 return fallback !== undefined ? fallback : undefined;
43 }
44
45 return predicate(value) ? value : fallback !== undefined ? fallback : undefined;
46}
Recursive structure cleanup
Removes unwanted values deeply within nested arrays and objects, not just top-level elements.
Highly customizable
Offers a
predicate
function to control what gets removed (defaults toBoolean
), giving developers flexibility for use cases like removingnull
,undefined
,''
, or domain-specific values.Preserves empty containers (optional)
The
preserveEmptyContainers
flag allows keeping empty arrays or objects, which may be required for API contracts or UI defaults.Fallback support
The
fallback
option allows returning a placeholder (e.g.,null
or0
) when a value would otherwise be removed-useful for data normalization.Safe and non-mutating
Returns a new deep structure without modifying the original input, preserving immutability.
Handles primitives, arrays, and plain objects uniformly
Robust handling of diverse data shapes makes it broadly applicable in real-world apps.
Tests | Examples
1test('compactDeep - default behavior', () => {
2 const input = { a: '', b: [0, false, 3], c: { d: null, e: 'ok' } };
3 expect(compactDeep(input)).toEqual({ b: [3], c: { e: 'ok' } });
4});
5
6test('compactDeep - preserve empty containers', () => {
7 const input = { a: '', b: [], c: { d: null } };
8 expect(compactDeep(input, { preserveEmptyContainers: true })).toEqual({ b: [], c: {} });
9});
10
11test('compactDeep - fallback replaces deleted values', () => {
12 const input = [0, 1, '', false, 'hello'];
13 expect(
14 compactDeep(input, { fallback: '__removed__' }))
15 .toEqual(['__removed__', 1, '__removed__', '__removed__', 'hello']
16 );
17});
18
19test('compactDeep - fallback for object field', () => {
20 const input = { a: '', b: 'ok' };
21 expect(compactDeep(input, { fallback: null })).toEqual({ a: null, b: 'ok' });
22});
23
24test('compactDeep - custom predicate', () => {
25 const input = ['short', 'longer', 'tiny'];
26 const result = compactDeep(input, { predicate: s => typeof s === 'string' && s.length > 4 });
27 expect(result).toEqual(['longer']);
28});
Common Use Cases
Form submission sanitization
Clean deeply nested user-submitted objects before sending to an API (e.g., remove empty fields or placeholders).
Data normalization before storage
Store only relevant, truthy values in databases or local storage to reduce clutter and improve consistency.
Serialization prep
Strip redundant or empty fields from objects before
JSON.stringify()
to minimize payload size.API response cleanup
When consuming APIs, compact the data before passing it into your application to simplify rendering logic.
Content filtering in CMS or editor apps
Remove empty content blocks, options, or irrelevant fields deeply within editor-generated structures.
Conditional UI rendering
Use it to transform props or state into a clean format before rendering dynamic layouts or conditional sections.