Or you can simply use flat npm package, which is a well known tested library. var flatten = require('flat') flatten(obj);
flat()” method is embedded in ES6 that enables you to “flatten” a nested JavaScript Array. This method returns a new array in which all of the elements of sub-arrays are concatenated according to the specified depth. Here, the “Array” object will invoke the “flat()” method while passing “depth” as an argument.
Here you go:
Object.assign({}, ...function _flatten(o) { return [].concat(...Object.keys(o).map(k => typeof o[k] === 'object' ? _flatten(o[k]) : ({[k]: o[k]})))}(yourObject))
Summary: recursively create an array of one-property objects, then combine them all with Object.assign
.
This uses ES6 features including Object.assign
or the spread operator, but it should be easy enough to rewrite not to require them.
For those who don't care about the one-line craziness and would prefer to be able to actually read it (depending on your definition of readability):
Object.assign(
{},
...function _flatten(o) {
return [].concat(...Object.keys(o)
.map(k =>
typeof o[k] === 'object' ?
_flatten(o[k]) :
({[k]: o[k]})
)
);
}(yourObject)
)
Simplified readable example, no dependencies
/**
* Flatten a multidimensional object
*
* For example:
* flattenObject({ a: 1, b: { c: 2 } })
* Returns:
* { a: 1, c: 2}
*/
export const flattenObject = (obj) => {
const flattened = {}
Object.keys(obj).forEach((key) => {
if (typeof obj[key] === 'object' && obj[key] !== null) {
Object.assign(flattened, flattenObject(obj[key]))
} else {
flattened[key] = obj[key]
}
})
return flattened
}
Working example: https://jsfiddle.net/webbertakken/jn613d8p/2/
Here is a true, crazy one-liner that flats the nested object recursively:
const flatten = (obj, roots=[], sep='.') => Object.keys(obj).reduce((memo, prop) => Object.assign({}, memo, Object.prototype.toString.call(obj[prop]) === '[object Object]' ? flatten(obj[prop], roots.concat([prop]), sep) : {[roots.concat([prop]).join(sep)]: obj[prop]}), {})
Multiline version, explained:
// $roots keeps previous parent properties as they will be added as a prefix for each prop.
// $sep is just a preference if you want to seperate nested paths other than dot.
const flatten = (obj, roots = [], sep = '.') => Object
// find props of given object
.keys(obj)
// return an object by iterating props
.reduce((memo, prop) => Object.assign(
// create a new object
{},
// include previously returned object
memo,
Object.prototype.toString.call(obj[prop]) === '[object Object]'
// keep working if value is an object
? flatten(obj[prop], roots.concat([prop]), sep)
// include current prop and value and prefix prop with the roots
: {[roots.concat([prop]).join(sep)]: obj[prop]}
), {})
An example:
const obj = {a: 1, b: 'b', d: {dd: 'Y'}, e: {f: {g: 'g'}}}
const flat = flatten(obj)
{
'a': 1,
'b': 'b',
'd.dd': 'Y',
'e.f.g': 'g'
}
Happy one-liner day!
It's not quite a one liner, but here's a solution that doesn't require anything from ES6. It uses underscore's extend
method, which could be swapped out for jQuery's.
function flatten(obj) {
var flattenedObj = {};
Object.keys(obj).forEach(function(key){
if (typeof obj[key] === 'object') {
$.extend(flattenedObj, flatten(obj[key]));
} else {
flattenedObj[key] = obj[key];
}
});
return flattenedObj;
}
Here are vanilla solutions that work for arrays, primitives, regular expressions, functions, any number of nested object levels, and just about everything else I could throw at them. The first overwrites property values in the manner that you would expect from Object.assign
.
((o) => {
return o !== Object(o) || Array.isArray(o) ? {}
: Object.assign({}, ...function leaves(o) {
return [].concat.apply([], Object.entries(o)
.map(([k, v]) => {
return (( !v || typeof v !== 'object'
|| !Object.keys(v).some(key => v.hasOwnProperty(key))
|| Array.isArray(v))
? {[k]: v}
: leaves(v)
);
})
);
}(o))
})(o)
The second accumulates values into an array.
((o) => {
return o !== Object(o) || Array.isArray(o) ? {}
: (function () {
return Object.values((function leaves(o) {
return [].concat.apply([], !o ? [] : Object.entries(o)
.map(([k, v]) => {
return (( !v || typeof v !== 'object'
|| !Object.keys(v).some(k => v.hasOwnProperty(k))
|| (Array.isArray(v) && !v.some(el => typeof el === 'object')))
? {[k]: v}
: leaves(v)
);
})
);
}(o))).reduce((acc, cur) => {
return ((key) => {
acc[key] = !acc[key] ? [cur[key]]
: new Array(...new Set(acc[key].concat([cur[key]])))
})(Object.keys(cur)[0]) ? acc : acc
}, {})
})(o);
})(o)
Also please do not include code like this in production as it is terribly difficult to debug.
function leaves1(o) {
return ((o) => {
return o !== Object(o) || Array.isArray(o) ? {}
: Object.assign({}, ...function leaves(o) {
return [].concat.apply([], Object.entries(o)
.map(([k, v]) => {
return (( !v || typeof v !== 'object'
|| !Object.keys(v).some(key => v.hasOwnProperty(key))
|| Array.isArray(v))
? {[k]: v}
: leaves(v)
);
})
);
}(o))
})(o);
}
function leaves2(o) {
return ((o) => {
return o !== Object(o) || Array.isArray(o) ? {}
: (function () {
return Object.values((function leaves(o) {
return [].concat.apply([], !o ? [] : Object.entries(o)
.map(([k, v]) => {
return (( !v || typeof v !== 'object'
|| !Object.keys(v).some(k => v.hasOwnProperty(k))
|| (Array.isArray(v) && !v.some(el => typeof el === 'object')))
? {[k]: v}
: leaves(v)
);
})
);
}(o))).reduce((acc, cur) => {
return ((key) => {
acc[key] = !acc[key] ? [cur[key]]
: new Array(...new Set(acc[key].concat([cur[key]])))
})(Object.keys(cur)[0]) ? acc : acc
}, {})
})(o);
})(o);
}
const obj = {
l1k0: 'foo',
l1k1: {
l2k0: 'bar',
l2k1: {
l3k0: {},
l3k1: null
},
l2k2: undefined
},
l1k2: 0,
l2k3: {
l3k2: true,
l3k3: {
l4k0: [1,2,3],
l4k1: [4,5,'six', {7: 'eight'}],
l4k2: {
null: 'test',
[{}]: 'obj',
[Array.prototype.map]: Array.prototype.map,
l5k3: ((o) => (typeof o === 'object'))(this.obj),
}
}
},
l1k4: '',
l1k5: new RegExp(/[\s\t]+/g),
l1k6: function(o) { return o.reduce((a,b) => a+b)},
false: [],
}
const objs = [null, undefined, {}, [], ['non', 'empty'], 42, /[\s\t]+/g, obj];
objs.forEach(o => {
console.log(leaves1(o));
});
objs.forEach(o => {
console.log(leaves2(o));
});
I like this code because it's a bit easier to understand.
Edit: I added some functionality I needed, so now it's a bit harder to understand.
const data = {
a: "a",
b: {
c: "c",
d: {
e: "e",
f: [
"g",
{
i: "i",
j: {},
k: []
}
]
}
}
};
function flatten(data, response = {}, flatKey = "", onlyLastKey = false) {
for (const [key, value] of Object.entries(data)) {
let newFlatKey;
if (!isNaN(parseInt(key)) && flatKey.includes("[]")) {
newFlatKey = (flatKey.charAt(flatKey.length - 1) == "." ? flatKey.slice(0, -1) : flatKey) + `[${key}]`;
} else if (!flatKey.includes(".") && flatKey.length > 0) {
newFlatKey = `${flatKey}.${key}`;
} else {
newFlatKey = `${flatKey}${key}`;
}
if (typeof value === "object" && value !== null && Object.keys(value).length > 0) {
flatten(value, response, `${newFlatKey}.`, onlyLastKey);
} else {
if(onlyLastKey){
newFlatKey = newFlatKey.split(".").pop();
}
if (Array.isArray(response)) {
response.push({
[newFlatKey.replace("[]", "")]: value
});
} else {
response[newFlatKey.replace("[]", "")] = value;
}
}
}
return response;
}
console.log(flatten(data));
console.log(flatten(data, {}, "data"));
console.log(flatten(data, {}, "data[]"));
console.log(flatten(data, {}, "data", true));
console.log(flatten(data, {}, "data[]", true));
console.log(flatten(data, []));
console.log(flatten(data, [], "data"));
console.log(flatten(data, [], "data[]"));
console.log(flatten(data, [], "data", true));
console.log(flatten(data, [], "data[]", true));
Demo https://stackblitz.com/edit/typescript-flatter
For insinde a typescript class use:
function flatten(data: any, response = {}, flatKey = "", onlyLastKey = false) {
for (const [key, value] of Object.entries(data)) {
let newFlatKey: string;
if (!isNaN(parseInt(key)) && flatKey.includes("[]")) {
newFlatKey = (flatKey.charAt(flatKey.length - 1) == "." ? flatKey.slice(0, -1) : flatKey) + `[${key}]`;
} else if (!flatKey.includes(".") && flatKey.length > 0) {
newFlatKey = `${flatKey}.${key}`;
} else {
newFlatKey = `${flatKey}${key}`;
}
if (typeof value === "object" && value !== null && Object.keys(value).length > 0) {
flatten(value, response, `${newFlatKey}.`, onlyLastKey);
} else {
if(onlyLastKey){
newFlatKey = newFlatKey.split(".").pop();
}
if (Array.isArray(response)) {
response.push({
[newFlatKey.replace("[]", "")]: value
});
} else {
response[newFlatKey.replace("[]", "")] = value;
}
}
}
return response;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With