Why Flatten / Unflatten?
- Sending data to APIs that expect flat structures
- Storing complex objects in databases (NoSQL, key-value stores)
- Comparing deeply nested objects more easily
- Working with form libraries (many expect flat keys)
- Serializing nested data into query strings or CSV-like formats
Flattening an object with dot notation
/** * Flattens a nested object into a single-level object with dot notation keys * @param {Object} obj - The object to flatten * @param {string} [prefix=''] - Internal: current key prefix * @returns {Object} Flattened object */function flattenObject(obj, prefix = '') { // Result object that will contain all flattened key-value pairs const result = {};
// Loop through every key in the current object for (const key in obj) { // Skip inherited properties (rare but good practice) if (!Object.prototype.hasOwnProperty.call(obj, key)) continue;
// Create the full key name (prefix + current key) const fullKey = prefix ? `${prefix}.${key}` : key;
// Get the value for current key const value = obj[key];
// Check if value is a non-null object (and not Array — we treat arrays as values) if (value !== null && typeof value === 'object' && !Array.isArray(value)) { // Recursive call: flatten nested object and merge into result Object.assign(result, flattenObject(value, fullKey)); } else { // Primitive value (or array/null) → add directly to result result[fullKey] = value; } }
return result;}
// ────────────────────────────────────────────────
const person = { name: 'John Smith', age: 28, address: { city: 'California', pincode: 12345, coordinates: { lat: 82.9716, lng: 47.5946, }, }, hobbies: ['teaching', 'coding'],};
const flat = flattenObject(person);
console.log(flat);/* Output:{ "name": "John Smith", "age": 28, "address.city": "California", "address.pincode": 12345, "address.coordinates.lat": 82.9716, "address.coordinates.lng": 47.5946, "hobbies": ["teaching", "coding"]}*/Flattening an object with custom separator with array support
/** * Advanced flatten with custom separator and array index notation * @param {Object} obj * @param {string} [separator='.'] * @param {string} [parentKey=''] * @returns {Object} */function flatten(obj, separator = '.', parentKey = '') { const result = {};
for (const key in obj) { if (!Object.prototype.hasOwnProperty.call(obj, key)) continue;
// Build current path const currentKey = parentKey ? `${parentKey}${separator}${key}` : key; const value = obj[key];
if (value !== null && typeof value === 'object') { // Handle arrays with index notation: hobbies.0, hobbies.1, ... if (Array.isArray(value)) { value.forEach((item, index) => { const arrayKey = `${currentKey}${separator}${index}`; Object.assign(result, flatten(item, separator, arrayKey)); }); } else { // Regular nested object Object.assign(result, flatten(value, separator, currentKey)); } } else { result[currentKey] = value; } }
return result;}
// ────────────────────────────────────────────────
const data = { user: { name: 'Jane Doe', tags: ['frontend', 'react', 'typescript'], }, settings: { theme: 'dark', notifications: { email: true, sms: false, }, },};
console.log(flatten(data, ' → ')); // using → as separator for clarity/* Output example:{ "user → name": "Jane Doe", "user → tags → 0": "frontend", "user → tags → 1": "react", "user → tags → 2": "typescript", "settings → theme": "dark", "settings → notifications → email": true, "settings → notifications → sms": false}*/Unflattening an object and rebuilding nested structure
/** * Unflattens a flat object back to nested structure * @param {Object} flatObj - Flat object with dot notation keys * @param {string} [separator='.'] * @returns {Object} Nested object */function unflatten(flatObj, separator = '.') { // Final nested result object const result = {};
// Process each flat key-value pair for (const flatKey in flatObj) { if (!Object.prototype.hasOwnProperty.call(flatObj, flatKey)) continue;
// Get the value const value = flatObj[flatKey];
// Split the key into parts (e.g. "a.b.c" → ["a", "b", "c"]) const keys = flatKey.split(separator);
// Current pointer that walks through the nested structure let current = result;
// Process all parts except the last one for (let i = 0; i < keys.length - 1; i++) { const key = keys[i];
// If key doesn't exist yet, create object or array depending on next key const nextKey = keys[i + 1]; const isNextNumeric = !isNaN(Number(nextKey)) && nextKey.trim() !== '';
if (!(key in current)) { current[key] = isNextNumeric ? [] : {}; }
// Move pointer deeper current = current[key]; }
// Last key → set the actual value const lastKey = keys[keys.length - 1]; current[lastKey] = value; }
return result;}
// ────────────────────────────────────────────────
const flatData = { 'profile.name': 'Mary', 'profile.age': 32, 'profile.skills.0': 'JavaScript', 'profile.skills.1': 'Node.js', 'config.api.timeout': 30000, 'config.api.key': 'xyz123',};
const nested = unflatten(flatData);
console.log(JSON.stringify(nested, null, 2));/* Output:{ "profile": { "name": "Mary", "age": 32, "skills": [ "JavaScript", "Node.js" ] }, "config": { "api": { "timeout": 30000, "key": "xyz123" } }}*/Use these helpers whenever you need to transform between nested and flat object representations in JavaScript — especially in frontend forms, backend APIs, configuration management, or database serialization.