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

cleanObjectDeep

Recursively removes null, undefined, and empty string values from an object or array.

TypeScript
Copied!
1/**
2 * Recursively removes null, undefined, and empty string values from an object or array.
3 *
4 * Options:
5 * - predicate: A function to determine if a value should be removed (defaults to null/undefined/'').
6 * - preserveEmptyObjects: If true, keeps empty objects after cleanup.
7 * - preserveEmptyArrays: If true, keeps empty arrays after cleanup.
8 * - fallback: A value to return instead of omitting the value.
9 *
10 * @param value - The input structure (object, array, or primitive).
11 * @param options - Optional configuration object.
12 * @returns A deeply cleaned version of the input or fallback if removed.
13 */
14export function cleanObjectDeep(
15  value: any,
16  options: {
17    predicate?: (value: any) => boolean;
18    preserveEmptyObjects?: boolean;
19    preserveEmptyArrays?: boolean;
20    fallback?: any;
21  } = {}
22): any {
23  const {
24    predicate = defaultPredicate,
25    preserveEmptyObjects = false,
26    preserveEmptyArrays = false,
27    fallback,
28  } = options;
29
30  if (Array.isArray(value)) {
31    const cleaned = value
32      .map(item => cleanObjectDeep(item, options))
33      .filter(item => !predicate(item));
34
35    if (cleaned.length === 0 && !preserveEmptyArrays) {
36      return fallback !== undefined ? fallback : undefined;
37    }
38
39    return cleaned;
40  }
41
42  if (value && typeof value === 'object' && value.constructor === Object) {
43    const result: Record<string, any> = {};
44    for (const [key, val] of Object.entries(value)) {
45      const cleaned = cleanObjectDeep(val, options);
46      if (!predicate(cleaned)) {
47        result[key] = cleaned;
48      }
49    }
50
51    if (Object.keys(result).length === 0 && !preserveEmptyObjects) {
52      return fallback !== undefined ? fallback : undefined;
53    }
54
55    return result;
56  }
57
58  return predicate(value) ? fallback !== undefined ? fallback : undefined : value;
59}
60
61function defaultPredicate(val: any): boolean {
62  return val === null || val === undefined || val === '';
63}
  • Recursive Deep Cleaning

    Traverses deeply nested structures (objects and arrays) to remove or replace unwanted values at any depth.

  • Highly Configurable Behavior

    Supports options like predicate, fallback, preserveEmptyObjects, and preserveEmptyArrays, offering fine-grained control.

  • Array and Object Support

    Treats both arrays and plain objects appropriately, preserving type-specific behavior during cleanup.

  • Custom Predicate Logic

    Allows users to define what "empty" means, enabling use cases beyond null/undefined/empty string.

  • Safe and Non-Destructive

    Returns new structures rather than mutating the original, ensuring immutability and functional safety.

Tests | Examples

TypeScript
Copied!
1test('cleanObjectDeep - fallback replaces removed values', () => {
2  const input = {
3    a: '',
4    b: null,
5    c: undefined,
6    d: { e: '', f: { g: null } },
7  };
8
9  expect(cleanObjectDeep(input, { fallback: 'REMOVED' })).toEqual({
10    a: 'REMOVED',
11    b: 'REMOVED',
12    c: 'REMOVED',
13    d: {
14      e: 'REMOVED',
15      f: { g: 'REMOVED' },
16    },
17  });
18});
19
20test('cleanObjectDeep - fallback returned for fully removed object', () => {
21  const input = { a: null, b: undefined };
22  expect(cleanObjectDeep(input, { fallback: 'FALLBACK' })).toEqual('FALLBACK');
23});
24
25test('cleanObjectDeep - fallback in nested arrays', () => {
26  const input = [null, '', { a: undefined }];
27  expect(cleanObjectDeep(input, { fallback: 0 })).toEqual([0, 0, { a: 0 }]);
28});
29
30test('cleanObjectDeep - fallback + preserveEmptyArrays/Objects', () => {
31  const input = {
32    arr: [null],
33    obj: { a: null },
34  };
35
36  expect(cleanObjectDeep(input, {
37    fallback: '-',
38    preserveEmptyArrays: true,
39    preserveEmptyObjects: true,
40  })).toEqual({
41    arr: [],
42    obj: {},
43  });
44});

Common Use Cases

  • Form Submission Sanitization

    Remove unused, empty, or irrelevant fields from deeply nested form data before sending to the server.

  • API Request Preparation

    Clean dynamic payloads before POST/PUT requests to reduce payload size and avoid validation errors.

  • Content Filtering and Normalization

    Clean user-generated or external data before storage or processing in a CMS, CRM, or database.

  • Config Validation and Compression

    Strip out empty overrides or default-equivalent values in deeply nested config trees.

  • Privacy and Redaction Tools

    Remove sensitive or empty data fields from logs, exports, or user-facing payloads.

Codebase: Utilities -> Objects -> cleanObjectDeep | Yevhen Klymentiev