Yevhen Klymentiev
dark
light
console
darkness
y.klymentiev@gmail.com
Reusable Snippets|Practical utility code for everyday use — custom-built and ready to share

compactDeep

Recursively removes values from arrays and objects based on a predicate.

TypeScript
Copied!
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 to Boolean), giving developers flexibility for use cases like removing null, 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 or 0) 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

TypeScript
Copied!
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.

Codebase: Utilities -> Arrays -> compactDeep | Yevhen Klymentiev