Page

Squash Object

QuestionImplement a function that returns a new object after squashing the input object into a single level of depth where nested keys are "squashed" together with a period delimiter (.).

Examples

const object = { a: 5, b: 6, c: { f: 9, g: { m: 17, n: 3, }, },};squashObject(object); // { a: 5, b: 6, 'c.f': 9, 'c.g.m': 17, 'c.g.n': 3 }Any keys with null-ish values (null and undefined) are still included in the returned object.const object = { a: { b: null, c: undefined },};squashObject(object); // { 'a.b': null, 'a.c': undefined }It should also work with properties that have arrays as the value:const object = { a: { b: [1, 2, 3], c: ['foo'] } };squashObject(object); // { 'a.b.0': 1, 'a.b.1': 2, 'a.b.2': 3, 'a.c.0': 'foo' }Empty keys should be treated as if that "layer" doesn't exist.const object = { foo: { '': { '': 1, 1bar: 2 }, },};squashObject(object); // { foo: 1, 'foo.bar': 2 }​


AnswerThis is a pretty tricky question, because not only do we need to traverse the object using recursion, but we also have to change the shape of the object, i.e. glueing all keys of a given path when we reach a primitive value. This also requires we keep track of the keys while traversing down the tree (the object).There are generally two ways we can explore an object:

  1. 1.Loop through the keys with the old school for ... in statement.

  2. 2.Converting the object into an array of keys with Object.keys(), or an array of a key-value tuple with Object.entries().

With the for ... in statement, inherited enumerable properties are be processed as well. So normally you'd add a Object.hasOwn() check to make sure the property is not inherited from its prototype. On the other hand, Object.keys() and Object.entries() only care about the properties directly defined on the object, and this is usually what we want.Here is how we would go about visiting each property. When the value of a given property is an object, we would have to repeat the process with recursion.function squashObject(object) { for (const [key, value] of Object.entries(object)) { if (typeof value !== 'object' || value !== null) { // Add props with glued/squashed keys. } else { // Recursion by calling squashObject. } }}We also need to keep track of the keys that are on the path to the current value so that we can squash them to form the new keys of the output object. To do that, we need to pass the keys down to the recursion call. We add a new parameter call path that is an array of strings that stores the key explored on a given path. When the current value is a primitive value, i.e. we are the end of the object, we join path together to form the key and assign the value to it.Here is the solution that defines an inner recursive helper function that accepts the path and output parameters.

Last updated