Deep Flattening Objects in JavaScript
When working with nested data structures in JavaScript, you may need to "flatten" an object. Flattening involves converting all nested keys into a single-level object where each key's path is concatenated with a dot (.
) to indicate its depth. This is particularly useful when working with deeply nested objects, and you need a simpler, flat representation for storage, transmission, or processing.
In this article, we’ll walk through how to deep flatten an object in JavaScript using two approaches: recursion and array conversion.
What is Object Flattening?
In the context of a deeply nested object, flattening means transforming the object so that:
- All nested objects are flattened into a single-level object.
- Nested keys are combined with their parent keys using a dot (
.
). - Arrays are flattened by assigning array indices as part of the key path.
Example of Flattening
Input:
const input = {
name: 'Pratap',
age: 25,
department: {
name: 'Software Developer',
section: 'Technical',
branch: {
name: 'Bangalore',
timezone: 'IST'
}
},
company: {
name: 'Anyname',
customers: ['Abc', 'Defg']
},
skills: ['javascript', 'node.js', 'mongodb']
};
Expected Output:
{
"name": "Pratap",
"age": 25,
"department.name": "Software Developer",
"department.section": "Technical",
"department.branch.name": "Bangalore",
"department.branch.timezone": "IST",
"company.name": "Anyname",
"company.customers.0": "Abc",
"company.customers.1": "Defg",
"skills.0": "javascript",
"skills.1": "node.js",
"skills.2": "mongodb"
}
As you can see, each nested key path is now part of a flattened structure, with the array indices incorporated into the key names.
Approaches to Deep Flatten an Object
We’ll explore two common approaches for flattening objects in JavaScript:
- Using Recursion
- Converting Arrays to Objects and Flattening
Approach: Using Recursion
Recursion is one of the most straightforward methods for flattening a deeply nested object. It works by checking each property of the object to determine if it's an object or array and then recursively processing it.
**Description: **
In this recursive approach:
- We loop through each key-value pair in the object.
- If the value is an object, we recursively call the function to flatten it further.
- If the value is an array, we treat the array indices as keys and flatten them.
- Non-object values are directly added to the final result.
const flatten = (obj, prefix) => {
let output = {};
for (let k in obj) {
let val = obj[k];
const type = Object.prototype.toString.call(val);
// If it's an object
if (type === "[object Object]") {
const newKey = prefix ? `${prefix}.${k}` : k;
const newObj = flatten(val, newKey);
output = { ...output, ...newObj };
}
// If it's an array
else if (type === "[object Array]") {
for (let i = 0; i < val.length; i++) {
const newKey = prefix ? `${prefix}.${k}.${i}` : `${k}.${i}`;
output = { ...output, [newKey]: val[i] };
}
}
// For normal values
else {
const newKey = prefix ? `${prefix}.${k}` : k;
output = { ...output, [newKey]: val };
}
}
return output;
};
Usage Example:
const input = {
name: 'Pratap',
age: 25,
department: {
name: 'Software Developer',
section: 'Technical',
branch: {
name: 'Bangalore',
timezone: 'IST'
}
},
company: {
name: 'Anyname',
customers: ['Abc', 'Defg']
},
skills: ['javascript', 'node.js', 'mongodb']
};
console.log(flatten(input));
{
"name": "Pratap",
"age": 25,
"department.name": "Software Developer",
"department.section": "Technical",
"department.branch.name": "Bangalore",
"department.branch.timezone": "IST",
"company.name": "Anyname",
"company.customers.0": "Abc",
"company.customers.1": "Defg",
"skills.0": "javascript",
"skills.1": "node.js",
"skills.2": "mongodb"
}
Approach: Convert Arrays to Objects and Flatten
An alternative approach is to convert arrays into objects before flattening. This method simplifies the flattening process by treating arrays just like objects. We can use modern ES6 features, such as spread
syntax, to achieve this.
Description:
In this approach:
- Arrays are spread into objects to avoid special handling.
- The same flattening function is applied uniformly to both arrays and objects.
const flatten = (obj, prefix) => {
let output = {};
for (let k in obj) {
let val = obj[k];
const newKey = prefix ? `${prefix}.${k}` : k;
// Check if the value is an object or array
if (typeof val === "object") {
if (Array.isArray(val)) {
// Convert array to object using spread
const { ...arrToObj } = val;
const newObj = flatten(arrToObj, newKey);
output = { ...output, ...newObj };
} else {
const newObj = flatten(val, newKey);
output = { ...output, ...newObj };
}
} else {
output = { ...output, [newKey]: val };
}
}
return output;
};
// Usage Example
const input = {
name: 'Pratap',
age: 25,
department: {
name: 'Software Developer',
section: 'Technical',
branch: {
name: 'Bangalore',
timezone: 'IST'
}
},
company: {
name: 'Anyname',
customers: ['Abc', 'Defg']
},
skills: ['javascript', 'node.js', 'mongodb']
};
console.log(flatten(input));
// Output
{
"name": "Pratap",
"age": 25,
"department.name": "Software Developer",
"department.section": "Technical",
"department.branch.name": "Bangalore",
"department.branch.timezone": "IST",
"company.name": "Anyname",
"company.customers.0": "Abc",
"company.customers.1": "Defg",
"skills.0": "javascript",
"skills.1": "node.js",
"skills.2": "mongodb"
}
Conclusion
Flattening deeply nested objects is a helpful technique when you need a flat representation of data for various reasons, such as storage, querying, or transmitting. The two approaches shown—recursion and array conversion—are flexible and cover most use cases. The recursion method is simple and straightforward, while the array conversion method leverages modern JavaScript features for cleaner code.