The problem statement
Given an object or array obj, return a compact object. A compact object is the same as the original object, except with keys containing falsy values removed. This operation applies to the object and any nested objects. Arrays are considered objects where the indices are keys. A value is considered falsy when Boolean(value) returns false.
You may assume the obj is the output of JSON.parse. In other words, it is valid JSON.
Examples:
Example 1:
Input: obj = [null, 0, false, 1]
Output: [1]
Explanation: All falsy values have been removed from the array.
Example 2:
Input: obj = {"a": null, "b": [false, 1]}
Output: {"b": [1]}
Explanation: obj["a"] and obj["b"][0] had falsy values and were removed.
Example 3:
Input: obj = [null, 0, 5, [0], [false, 16]]
Output: [5, [], [16]]
Explanation: obj[0], obj[1], obj[3][0], and obj[4][0] were falsy and removed.
Understanding the problem
This problem is asking us to filter out any values within a multi-dimensional array / object that evaluate to false. A few things to note:
- An array with values equals true in a
Boolean()consumption. - 0 evaluates to false along with null, undefined etc.
Recursion is going to be the method of choice here to solve this particular problem.
We know from the type provided that the input to the func will be an array or an object. So we can safely assume that we can recursively call this func with any value that we might need.
We will need to differentiate between whether the input is an array or an object as there will be a different process of returning either of them. I.e. array.push vs obj[key] = val.
Code
type JSONValue =
| null
| boolean
| number
| string
| JSONValue[]
| { [key: string]: JSONValue };
type Obj = Record<string, JSONValue> | Array<JSONValue>;
function compactObject(obj: Obj): Obj {
// If the incoming obj is an array, only return those where Boolean is true
// A sub-array is true.
// Else just set data is the key-value object.
const data = Array.isArray(obj) ? obj.filter(Boolean) : obj;
return Object.keys(data).reduce(
(acc, key) => {
const value = data[key];
if (Boolean(value)) {
// if we have an object which can be a key-value obj or array in js, then recursively call ourselves. Else just set the value for the position.
acc[key] = typeof value === "object" ? compactObject(value) : value;
}
return acc;
},
Array.isArray(obj) ? [] : {}
);
}
Code Explanation
The approach:
- Set a const called data, its value is based upon whether the input param
objis an array or not. If it is an array, we run thefilterprototype on the array to get rid of all values which are falsy. Else we just return the key-value based object. - We then create a new array using
Object.keys, for an array input toObject.keysit will simply give you the values. For an object input it will give you all the keys.- Then we perform the
reduceprototype on the array- If obj is an array then the initial value is set to
[]else its set to an empty{}.
- If obj is an array then the initial value is set to
- We then get the value iterated by looking up data by the key index from
Object.keys; - If that value is true, then we set the
valuein ouraccumulatorat indexkeyto either be a recursively called instance of ourself if value is an object (which in js can mean an object or array), or simply the value in question which could be a primitive such as string, number etc. - Then we return the accumulated response (our resulting array or object).
- Then we perform the
This allows us to perform a depth first traversal through data to process any sub-arrays / objects, bubbling up their results so we iterate logically over each index at a time.
Key takeaways
Boolean()can result intrueforarrays,numbersetc.Array.reduceis a really helpful tool for processing / transformingarraysandobjects. Gives a nice alternative tomap().- When thinking about composing a recursion based solution. Think of implementing your function at the bottom node upwards. I keep approaching these problems with a top downwards mindset and end up over-engineering it and getting lost. It boils down to results bubbling up from the bottom and how they then get processed further up.