Best way to flatten JS object (keys and values) to a single depth array
I wanted to flatten my deep object to one level depth. None of the above solutions worked for me.
My input:
{
"user": {
"key_value_map": {
"CreatedDate": "123424",
"Department": {
"Name": "XYZ"
}
}
}
}
Expected output:
{
"user.key_value_map.CreatedDate": "123424",
"user.key_value_map.Department.Name": "XYZ"
}
Code that worked for me:
function flattenObject(ob) {
var toReturn = {};
for (var i in ob) {
if (!ob.hasOwnProperty(i)) continue;
if ((typeof ob[i]) == 'object' && ob[i] !== null) {
var flatObject = flattenObject(ob[i]);
for (var x in flatObject) {
if (!flatObject.hasOwnProperty(x)) continue;
toReturn[i + '.' + x] = flatObject[x];
}
} else {
toReturn[i] = ob[i];
}
}
return toReturn;
}
Flattening Object can be done using recursion as below :
Sample Input
const obj = {
name: "test",
address: {
personal: "abc",
office: {
building: 'random',
street: 'some street'
}
}
}
Expected Output
{
name : "test",
address_personal: "abc"
address_office_building: "random"
address_office_street: "some street"
}
My Solution
function flattenObj(obj, parent, res = {}){
for(let key in obj){
let propName = parent ? parent + '_' + key : key;
if(typeof obj[key] == 'object'){
flattenObj(obj[key], propName, res);
} else {
res[propName] = obj[key];
}
}
return res;
}
Hope it helps
You could just concat all keys and values. (It does not solve the type casting to number for keys.)
var object = { 0: [1, 2, 3, 4] },
result = Object.keys(object).reduce(function (r, k) {
return r.concat(k, object[k]);
}, []);
console.log(result);
This answer is an improvement of @Muthukrishnan 's answer
If you want to flatten an object deeply outputting the values into a one level deep object keyed with the path of the value in the previous object
(eg: { foo: { bar: 'baz'} }
=> { 'foo.bar': 'baz' }
)
Here is how you can effectively do it:
/**
* @param ob Object The object to flatten
* @param prefix String (Optional) The prefix to add before each key, also used for recursion
**/
function flattenObject(ob, prefix = false, result = null) {
result = result || {};
// Preserve empty objects and arrays, they are lost otherwise
if (prefix && typeof ob === 'object' && ob !== null && Object.keys(ob).length === 0) {
result[prefix] = Array.isArray(ob) ? [] : {};
return result;
}
prefix = prefix ? prefix + '.' : '';
for (const i in ob) {
if (Object.prototype.hasOwnProperty.call(ob, i)) {
if (typeof ob[i] === 'object' && ob[i] !== null) {
// Recursion on deeper objects
flattenObject(ob[i], prefix + i, result);
} else {
result[prefix + i] = ob[i];
}
}
}
return result;
}
/**
* Bonus function to unflatten an object
*
* @param ob Object The object to unflatten
*/
function unflattenObject(ob) {
const result = {};
for (const i in ob) {
if (Object.prototype.hasOwnProperty.call(ob, i)) {
const keys = i.match(/(?:^\.+)?(?:\.{2,}|[^.])+(?:\.+$)?/g); // Just a complicated regex to only match a single dot in the middle of the string
keys.reduce((r, e, j) => {
return r[e] || (r[e] = isNaN(Number(keys[j + 1])) ? (keys.length - 1 === j ? ob[i] : {}) : []);
}, result);
}
}
return result;
}
// TESTS
const obj = {
value: {
foo: {
bar: 'yes',
so: {
freakin: {
nested: 'Wow',
}
}
},
},
// Some edge cases to test
test: [true, false, [null, undefined, 1]],
not_lost: [], // Empty arrays should be preserved
not_lost2: {}, // Empty objects should be preserved
// Be careful with object having dots in the keys
'I.like.dots..in.object.keys...': "... Please don't override me",
I: {
like: {
'dots..in': {
object: {
'keys...': "You've been overwritten"
}
}
}
}
};
console.log(flattenObject(['I', {'am': 'an array'}]));
let flat = flattenObject(obj);
console.log(flat, unflattenObject(flat));
There is an obvious problem that you could encounter with flattening this way if your object contains keys with dots, this is documented in the fiddle