how to convert this nested object into a flat object?
Sorry I don't know how to phrase the question title. Please help edit if possible.
I have an object like this:
{
a: 'jack',
b: {
c: 'sparrow',
d: {
e: 'hahaha'
}
}
}
I want to make it look like:
{
'a': 'jack',
'b.c': 'sparrow',
'b.d.e': 'hahaha'
}
// so that I can use it this way:
a['b.d.e']
jQuery is ok too. I know for the nested object, I can use a.b.d.e
to get hahaha
, but today I have to use it like a['b.d.e']
-_-!!!
How can I achieve this? Thanks in advance :)
Solution 1:
You could use a recursive function to crawl the object and flatten it for you.
var test = {
a: 'jack',
b: {
c: 'sparrow',
d: {
e: 'hahaha'
}
}
};
function traverseAndFlatten(currentNode, target, flattenedKey) {
for (var key in currentNode) {
if (currentNode.hasOwnProperty(key)) {
var newKey;
if (flattenedKey === undefined) {
newKey = key;
} else {
newKey = flattenedKey + '.' + key;
}
var value = currentNode[key];
if (typeof value === "object") {
traverseAndFlatten(value, target, newKey);
} else {
target[newKey] = value;
}
}
}
}
function flatten(obj) {
var flattenedObject = {};
traverseAndFlatten(obj, flattenedObject);
return flattenedObject;
}
var flattened = JSON.stringify(flatten(test));
console.log(flattened);
Solution 2:
An alternative recursive implementation. I just felt like writing one implementation myself, even though the current ones are already really good.
The recursive function checks whether the key is of type 'object'
.
- If it's an object, we iterate by each object's key.
- Else, we add it into our result object.
function flat(res, key, val, pre = '') {
const prefix = [pre, key].filter(v => v).join('.');
return typeof val === 'object'
? Object.keys(val).reduce((prev, curr) => flat(prev, curr, val[curr], prefix), res)
: Object.assign(res, { [prefix]: val});
}
return Object.keys(input).reduce((prev, curr) => flat(prev, curr, input[curr]), {});
Flat NPM package
Or you can simply use flat npm package, which is a well known tested library.
var flatten = require('flat')
flatten(obj);
⬑ I would use this in serious code.
[Extra] Neater call to the function above
function flatObject(input) {
function flat(res, key, val, pre = '') {
const prefix = [pre, key].filter(v => v).join('.');
return typeof val === 'object'
? Object.keys(val).reduce((prev, curr) => flat(prev, curr, val[curr], prefix), res)
: Object.assign(res, { [prefix]: val});
}
return Object.keys(input).reduce((prev, curr) => flat(prev, curr, input[curr]), {});
}
const result = flatObject(input);
[Extra] Demo
http://codepen.io/zurfyx/pen/VpErja?editors=1010
function flatObject(input) {
function flat(res, key, val, pre = '') {
const prefix = [pre, key].filter(v => v).join('.');
return typeof val === 'object'
? Object.keys(val).reduce((prev, curr) => flat(prev, curr, val[curr], prefix), res)
: Object.assign(res, { [prefix]: val});
}
return Object.keys(input).reduce((prev, curr) => flat(prev, curr, input[curr]), {});
}
const result = flatObject({
a: 'jack',
b: {
c: 'sparrow',
d: {
e: 'hahaha'
}
}
});
document.getElementById('code').innerHTML = JSON.stringify(result, null, 2);
<pre><code id="code"></code></pre>
Solution 3:
You could loop through the entries of the object. If the value
is an object, recursively call the function. Use flatMap
to get a flattened array of entries.
Then use Object.fromEntries()
to get an object from the flattened array of entries
const input = {
a: 'jack',
b: {
c: 'sparrow',
d: {
e: 'hahaha'
}
}
}
const getEntries = (o, prefix = '') =>
Object.entries(o).flatMap(([k, v]) =>
Object(v) === v ? getEntries(v, `${prefix}${k}.`) : [ [`${prefix}${k}`, v] ]
)
console.log(
Object.fromEntries(getEntries(input))
)
Note: Object(v) === v
returns true
only for objects. typeof v === 'object'
is true for v = null
too.
Solution 4:
Option 1: export a flat object with just the Leaves. i.e object exported contains just paths with primitive value at the end ( see example ) .
//recursion: walk on each route until the primitive value.
//Did we found a primitive?
//Good, then join all keys in the current path and save it on the export object.
export function flatObject(obj) {
const flatObject = {};
const path = []; // current path
function dig(obj) {
if (obj !== Object(obj))
/*is primitive, end of path*/
return flatObject[path.join('.')] = obj; /*<- value*/
//no? so this is an object with keys. go deeper on each key down
for (let key in obj) {
path.push(key);
dig(obj[key]);
path.pop();
}
}
dig(obj);
return flatObject;
}
Example
let obj = {aaa:{bbb:{c:1,d:7}}, bb:{vv:2}}
console.log(flatObject(obj))
/*
{
"aaa.bbb.c": 1,
"aaa.bbb.d": 7,
"bb.vv": 2
}
*/
Option 2: export a flat object with all intermidate paths. a little bit shorter and simpler (see example).
export function flatObject(obj) {
const flatObject = {};
const path = []; // current path
function dig(obj) {
for (let key in obj) {
path.push(key);
flatObject[path.join('.')] = obj[key];
dig(obj[key])
path.pop();
}
}
dig(obj);
return flatObject;
}
Example:
let obj = {aaa:{bbb:{c:1,d:7}}, bb:{vv:2}}
console.log(flatObject(obj))
/*{
"aaa": {
"bbb": {
"c": 1,
"d": 7
}
},
"aaa.bbb": {
"c": 1,
"d": 7
},
"aaa.bbb.c": 1,
"aaa.bbb.d": 7,
"bb": {
"vv": 2
},
"bb.vv": 2
}
*/