back to series

Implement a squashObjectKeys function

Squashing keys refers to the process of transforming a nested objects into a single-level object by flattening out the keys of the object.

Variation: Basic implementation

1. Basic implementation

Squash keys for an object and use . as seperator. For array values use index as keys.

./src/squashObjectKeys.js
import getSquashedKey from './src/utils.js';
function squashObjectKeys(input) {
const resultObj = {};
function squashObjectKeys(inputObj, parentKey) {
for (const key in inputObj) {
// only perform operation for non inherited keys
if (Object.hasOwn(inputObj, key)) {
const value = inputObj[key];
// utility get new key which is squashed
const newKey = getSquashedKey(parentKey, key);
if (typeof value !== 'object') {
resultObj[newKey] = value;
} else {
squashObjectKeys(value, newKey);
}
}
}
}
squashObjectKeys(input, null);
return resultObj;
}
// Usage
const input = {
a: 1,
b: 2,
c: {
d: 12,
e: {
f: 'Hello World',
g: [12, 14]
}
}
};
const output = squashObjectKeys(input);
/**
* Yields:
* {
"a": 1,
"b": 2,
"c.d": 12,
"c.e.f": "Hello World",
"c.e.g.0": 12,
"c.e.g.1": 14
}
*/
console.log(output);

The utility to generate new keys which are squashed can be implemented as follows:

./src/utils.js
function getSquashedKey(parentKey, currentKey) {
// when null, return the same key (for root keys )
if (Object.is(parentKey, null)) {
return currentKey;
}
return `${parentKey}.${currentKey}`;
}
Variation: With depth

2. With depth implementation

In above example, we squashed all keys for any depth of the object or array. In this case, we should only squash keys until a certain depth of the object.

As most of the implementation remains exactly same, I will highlight only the newer additions made on the utility.

./src/squashObjectKeys.js
function squashObjectKeys(input, depth) {
const resultObj = {};
function squashObjectKeys(inputObj, parentKey, depth) {
for (const key in inputObj) {
// only perform operation for non inherited keys
if (Object.hasOwn(inputObj, key)) {
const value = inputObj[key];
// get new key which is squashed
const newKey = getSquashedKey(parentKey, key);
if (typeof value !== 'object') {
resultObj[newKey] = value;
} else {
if (depth > 0) {
squashObjectKeys(value, newKey, depth - 1);
} else {
resultObj[key] = value;
}
}
}
}
}
squashObjectKeys(input, null, depth);
return resultObj;
}
// Usage
const input = {
a: 1,
b: 2,
c: {
// depth 1
d: 12,
e: {
// depth 2
f: 'Hello World',
g: [12, 14], // depth 3
h: {
// depth 3
i: 'Hola!'
}
}
}
};
const output = squashObjectKeys(input, 2);
/**
* Yields:
* {
"a": 1,
"b": 2,
"c.d": 12,
"c.e.f": "Hello World",
"g": [12,14],
"h": {
"i": "Hola!"
}
}
*/
console.log(output);
Variation: Empty Keys

3. With empty keys implementation

Since object keys are strings, there can be cases where some keys maybe be passed as empty spaces. In such cases, we simply need to ignore the key, instead of generating squashed keys with empty spaces in between like const key='a.b.c.''.d'

For this implementation we don’t really need to anything within the squashObjectKeys function, we can simply alter the getSquashedKey utility created as such:

./src/utils.js
function getSquashedKey(parentKey, currentKey) {
if (Object.is(parentKey, null)) {
return currentKey;
}
if (!currentKey || !currentKey.trim().length) {
return parentKey;
}
return `${parentKey}.${currentKey}`;
}
// Output changes
// Usage
const input = {
a: 1,
b: 2,
c: {
d: 12,
'': {
f: {
'': {
h: 16
}
}
}
}
};
const output = squashObjectKeys(input);
/**
* Yields:
* {
"a": 1,
"b": 2,
"c.d": 12,
"c.f.h": 16
}
}
*/
console.log(output);

Further Reading

I strongly encourage you to explore and tackle additional questions in my Recursion Questions for Frontend Interviews blog series.

By doing so, you can enhance your understanding and proficiency with recursion, preparing you to excel in your upcoming frontend interviews.

Wishing you best. Happy Interviewing 🫡.