Deep Remove Falsy

Medium

Prompt

Implement a function deepRemoveFalsy(value) that returns a new object with all falsy values removed, including falsy values that are deeply-nested. You can assume the value only contains JSON-serializable values (null, boolean, number, string, Array, Object) and will not contain any other objects like Date, Regex, Map or Set.

The values false, null, 0, '' (empty string), undefined, and NaN are falsy.

Requirements

  • The function should handle any JSON-serializable value (primitives, arrays, and objects)
  • It should return a new object/array with all falsy values removed at all nested levels
  • The original value should not be modified
  • Empty objects and arrays (after removing falsy values) should be removed
  • You should consider false, null, 0, '', undefined, and NaN as falsy values

Example

// Arrays
deepRemoveFalsy([0, 1, false, 2, '', 3, null]); // [1, 2, 3]

// Objects
deepRemoveFalsy({ a: null, b: [false, 1] }); // { b: [1] }

// Nested structures
deepRemoveFalsy({
a: null,
b: [false, { c: 0, d: 2 }],
e: { f: '', g: 'hello' }
}); // { b: [{ d: 2 }], e: { g: 'hello' } }

// Empty objects/arrays should be removed
deepRemoveFalsy({
a: { b: null, c: {} },
d: [null, []]
}); // {}

Playground

Hint 1

You'll need to use recursion to handle nested objects and arrays.

Hint 2

For objects, check if they're empty after processing. If an object has no remaining properties, don't include it in the result.

Solution

Explanation

This solution tackles the problem with a clean, recursive approach. Let's break down how it works:

For primitives, we:

This first step performs a quick check for primitive values (strings, numbers, booleans) or null. For these values, we simply return them as-is. Since we're only removing falsy values from objects and arrays, if the value itself is falsy but not an object or array, it just passes through.

if (typeof value !== 'object' || value == null) {
return value;
}

For arrays, we:

  • Create a new array to hold the result (to avoid modifying the original)
  • Iterate through each element using forEach()
  • Perform a boolean check with if (item) to filter out falsy values
  • For truthy values, recursively process them and add to our result array
if (Array.isArray(value)) {
const filteredArray = [];
value.forEach((item) => {
if (item) {
filteredArray.push(deepRemoveFalsy(item));
}
});

return filteredArray;
}

For objects, we:

  • Create a new object with Object.create(null), which creates a "pure" object with no prototype
  • Use Object.entries() to get all key-value pairs
  • Iterate through the pairs with forEach()
  • Check if each value is truthy with if (val)
  • For truthy values, recursively process them and add to our result object
const filteredObject = Object.create(null);
Object.entries(value).forEach(([key, val]) => {
if (val) {
filteredObject[key] = deepRemoveFalsy(val);
}
});
return filteredObject;

The recursion is what allows this solution to handle deeply nested structures. When a value contains nested objects or arrays, the function calls itself with those nested values, allowing it to clean falsy values at all levels.

Two clever aspects of this implementation are:

  1. Implicit falsy checking: Using if (item) or if (val) leverages JavaScript's type coercion to automatically filter out all falsy values (false, null, 0, '', undefined, and NaN).

  2. Clean object creation: Using Object.create(null) creates objects without the standard Object prototype, which can make the resulting objects slightly more efficient.

This solution takes advantage of JavaScript's built-in behavior regarding truthy and falsy values, resulting in clean, readable code that effectively removes falsy values at all levels of nested objects and arrays.

00:00