Skip to main content

Object Related Problems

Flatten an Object

Recursive Way

const obj = {
a: 1,
b: {
c: 3,
d: {
e: 5,
},
},
};

const flatten = (obj, sep, parent = "") => {
return Object.keys(obj).reduce((acc, curr) => {
let newKey = parent ? parent + sep + curr : curr;
if (typeof obj[curr] === "object" && !Array.isArray(obj[curr])) {
acc = {
...acc,
...flatten(obj[curr], sep, newKey),
};
} else acc[newKey] = obj[curr];
return acc;
}, {});
};

console.log(flatten(obj, ".")); //{ a: 1, 'b.c': 3, 'b.d.e': 5 }

Stack Based

function flattenObject(obj) {
const result = {};
const stack = [{ parentKey: '', value: obj }];

while (stack.length > 0) {
const { parentKey, value } = stack.pop();

for (const [key, val] of Object.entries(value)) {
const newKey = parentKey ? `${parentKey}.${key}` : key;

if (typeof val === 'object' && val !== null && !Array.isArray(val)) {
stack.push({ parentKey: newKey, value: val });
} else {
result[newKey] = val;
}
}
}

return result;
}

// Example object to flatten
const obj = {
a: 1,
b: {
c: 2,
d: {
e: 3,
f: 4
}
},
g: 5
};

console.log(flattenObject(obj)); //{ a: 1, g: 5, 'b.c': 2, 'b.d.e': 3, 'b.d.f': 4 }

In Place implementation

We are not using extra space for this case.

function flattenObjectInPlace(obj, parentKey = '') {
const keys = Object.keys(obj);

for (const key of keys) {
const newKey = parentKey ? `${parentKey}.${key}` : key;

if (typeof obj[key] === 'object' && obj[key] !== null) {
// Recurse for nested objects
flattenObjectInPlace(obj[key], newKey);

// After processing nested object, copy its flattened properties up
for (const childKey in obj[key]) {
obj[childKey] = obj[key][childKey];
}
// Delete the original nested object
delete obj[key];
} else if (parentKey) {
// For primitive values in nested objects, move them up with the new key
obj[newKey] = obj[key];
delete obj[key];
}
}
}

// // Example usage:
const nestedObject = {
user: {
name: "Alice",
address: {
city: "Wonderland",
postalCode: "12345"
},
preferences: {
theme: "dark",
language: "en"
}
}
};

console.log(JSON.stringify(flattenObjectInPlace(nestedObject), null, 2));
/*
{
"user.name": "Alice",
"user.address.city": "Wonderland",
"user.address.postalCode": "12345",
"user.preferences.theme": "dark",
"user.preferences.language": "en"
}
*/

Extended Version of Object Flattening

const obj = {
a: 1,
b: {
c: 2,
d: {
e: 4,
f: [1, 2, { g: 5, h: { i: 6, x: [22, 11] } }]
}
}
}


const flattenObj = (obj, parent = "") => {
return Object.keys(obj).reduce((acc, curr) => {
const newKey = parent ? `${parent}.${curr}` : curr
if (typeof obj[curr] === 'object' && typeof obj[curr] !== null && !Array.isArray(obj[curr])) {
acc = {
...acc,
...flattenObj(obj[curr], newKey)
}
}

else if (Array.isArray(obj[curr])) {
obj[curr].forEach((item, index) => {
const newArrKey = `${newKey}[${index}]`
if (typeof item === 'object' && item !== null) {
acc = { ...acc, ...flattenObj(item, newArrKey) }
}
else acc[newArrKey] = item
})
}
else {
acc[newKey] = obj[curr]
}
return acc
}, {})

}

console.log(flattenObj(obj));

/*
{
a: 1,
'b.c': 2,
'b.d.e': 4,
'b.d.f[0]': 1,
'b.d.f[1]': 2,
'b.d.f[2].g': 5,
'b.d.f[2].h.i': 6,
'b.d.f[2].h.x[0]': 22,
'b.d.f[2].h.x[1]': 11
}
*/

Extended Version of Object Flattening (In Place)

function flattenObject(obj, parent = '', res = obj) {
// Helper function to process array values
const processArray = (arr, parentKey) => {
arr.forEach((item, index) => {
if (typeof item === 'object' && item !== null) {
flattenObject(item, `${parentKey}[${index}].`, res);
} else {
res[`${parentKey}[${index}]`] = item;
}
});
};

// Iterate over object keys
Object.keys(obj).forEach(key => {
const value = obj[key];
const newKey = parent + key

if (Array.isArray(value)) {
// Process arrays
processArray(value, newKey);
delete obj[key]; // Remove the original array key after processing
} else if (typeof value === 'object' && value !== null) {
// Process nested objects
flattenObject(value, newKey + '.', res);
delete obj[key]; // Remove the original nested object key after processing
} else {
// Handle simple values
if (parent) {
res[newKey] = value;
delete obj[key]; // Remove the original key to avoid duplication
}
}
});

return res;
}

let obj = {
a: {
b: 1,
c: {
d: 2,
e: [3, 4]
}
},
f: [5, { g: 6 }]
};

flattenObject(obj);
console.log(obj);

/*
{
'a.b': 1,
'a.c.d': 2,
'a.c.e[0]': 3,
'a.c.e[1]': 4,
'f[0]': 5,
'f[1].g': 6
}
*/

Group By

const people = [
{ id: 1, name: "Alice", age: 25, city: "New York" },
{ id: 2, name: "Bob", age: 30, city: "San Francisco" },
{ id: 3, name: "Charlie", age: 28, city: "New York" },
{ id: 4, name: "David", age: 25, city: "Los Angeles" },
];

const groupBy = (obj, keyGetter) => {
return obj.reduce((acc, curr) => {
const key = keyGetter(curr);
if (!acc[key]) acc[key] = [];
acc[key].push(curr);
return acc;
}, {});
};

console.log(groupBy(people, (item) => item.city));

/*
{
'New York': [
{ id: 1, name: 'Alice', age: 25, city: 'New York' },
{ id: 3, name: 'Charlie', age: 28, city: 'New York' }
],
'San Francisco': [ { id: 2, name: 'Bob', age: 30, city: 'San Francisco' } ],
'Los Angeles': [ { id: 4, name: 'David', age: 25, city: 'Los Angeles' } ]
}
*/

Add Keys

const obj1 = {
a: 1,
b: 2,
c: 4
}

const obj2 = {
a: 1,
b: 2,
c: 4
}

const obj3 = {
e: 2,
}

const addKeys = (...objs) => {
return objs?.reduce((acc, curr) => {
for (const key in curr) {
if (!acc[key]) acc[key] = 0
acc[key] += curr[key]
}
return acc
}, {})
}

console.log(addKeys(obj1, obj2, obj3)); //{ a: 2, b: 4, c: 8, e: 2 }

Deep Copy

const obj = {
a: 1,
b: {
c: 3,
d: {
e: 5,
},
},
};

const deepCopy = (obj) => {
if (typeof obj !== "object" || obj === null) return obj;

const copiedObj = Array.isArray(obj) ? [] : {};

for (const key in obj) {
if (Object.hasOwn(obj, key)) {
copiedObj[key] = deepCopy(obj[key]);
}
}
return copiedObj;
};

const newObj = deepCopy(obj);
newObj.a = 10;
newObj.b.c = 11;
newObj.b.d.e = 15;

console.log(obj); //{ a: 1, b: { c: 3, d: { e: 5 } } }
console.log(newObj); //{ a: 10, b: { c: 11, d: { e: 15 } } }

Deep Equal

function deepEqual(a, b) {
if (a === b) {
return true;
}

if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null) {
return false;
}

// Get all property keys
const keysA = Object.keys(a);
const keysB = Object.keys(b);

// Check if the number of properties is the same
if (keysA.length !== keysB.length) {
return false;
}

// Check if all keys and values are equal
for (let key of keysA) {
if (!keysB.includes(key) || !deepEqual(a[key], b[key])) {
return false;
}
}

return true;
}

console.log(deepEqual([1, 2, 3], [1, 2, 3])); //true
console.log(deepEqual([1, 2, 3], [1, 2])); //false
console.log(deepEqual({ x: 3 }, { x: 3 })); //true
console.log(deepEqual({ x: 3 }, { x: 4 })); //false