I need to flatten a nested object. Need a one liner. Not sure what the correct term for this process is.
I can use pure Javascript or libraries, I particularly like underscore.
I've got ...
{
a:2,
b: {
c:3
}
}
And I want ...
{
a:2,
c:3
}
I've tried ...
var obj = {"fred":2,"jill":4,"obby":{"john":5}};
var resultObj = _.pick(obj, "fred")
alert(JSON.stringify(resultObj));
Which works but I also need this to work ...
var obj = {"fred":2,"jill":4,"obby":{"john":5}};
var resultObj = _.pick(obj, "john")
alert(JSON.stringify(resultObj));
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) => {
const value = obj[key]
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
Object.assign(flattened, flattenObject(value))
} else {
flattened[key] = value
}
})
return flattened
}
Features
No dependencies
Works with null values
Works with arrays
Working example https://jsfiddle.net/webbertakken/jn613d8p/26/
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!
ES6 Native, Recursive:
One-liner
const crushObj = (obj) => Object.keys(obj).reduce((acc, cur) => typeof obj[cur] === 'object' ? { ...acc, ...crushObj(obj[cur]) } : { ...acc, [cur]: obj[cur] } , {})
Expanded
const crushObj = (obj = {}) => Object.keys(obj || {}).reduce((acc, cur) => {
if (typeof obj[cur] === 'object') {
acc = { ...acc, ...crushObj(obj[cur])}
} else { acc[cur] = obj[cur] }
return acc
}, {})
Usage
const obj = {
a:2,
b: {
c:3
}
}
const crushed = crushObj(obj)
console.log(crushed)
// { a: 2, c: 3 }
My ES6 version:
const flatten = (obj) => {
let res = {};
for (const [key, value] of Object.entries(obj)) {
if (typeof value === 'object') {
res = { ...res, ...flatten(value) };
} else {
res[key] = value;
}
}
return res;
}
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;
}
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;
}
This is a function I've got in my common libraries for exactly this purpose.
I believe I got this from a similar stackoverflow question, but cannot remember which (edit: Fastest way to flatten / un-flatten nested JSON objects - Thanks Yoshi!)
function flatten(data) {
var result = {};
function recurse (cur, prop) {
if (Object(cur) !== cur) {
result[prop] = cur;
} else if (Array.isArray(cur)) {
for(var i=0, l=cur.length; i<l; i++)
recurse(cur[i], prop + "[" + i + "]");
if (l == 0)
result[prop] = [];
} else {
var isEmpty = true;
for (var p in cur) {
isEmpty = false;
recurse(cur[p], prop ? prop+"."+p : p);
}
if (isEmpty && prop)
result[prop] = {};
}
}
recurse(data, "");
return result;
}
This can then be called as follows:
var myJSON = '{a:2, b:{c:3}}';
var myFlattenedJSON = flatten(myJSON);
You can also append this function to the standard Javascript string class as follows:
String.prototype.flattenJSON = function() {
var data = this;
var result = {};
function recurse (cur, prop) {
if (Object(cur) !== cur) {
result[prop] = cur;
} else if (Array.isArray(cur)) {
for(var i=0, l=cur.length; i<l; i++)
recurse(cur[i], prop + "[" + i + "]");
if (l == 0)
result[prop] = [];
} else {
var isEmpty = true;
for (var p in cur) {
isEmpty = false;
recurse(cur[p], prop ? prop+"."+p : p);
}
if (isEmpty && prop)
result[prop] = {};
}
}
recurse(data, "");
return result;
}
With which, you can do the following:
var flattenedJSON = '{a:2, b:{c:3}}'.flattenJSON();
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));
});
Here's an ES6 version in TypeScript. It takes the best of answers given here and elsewhere. Some features:
Supports Date objects and converts them into ISO strings
Puts an underscore between the parent's and child's key (e.g. {a: {b: 'test'}} becomes {a_b: 'test'}
const flatten = (obj: Record<string, unknown>, parent?: string): Record<string, unknown> => {
let res: Record<string, unknown> = {}
for (const [key, value] of Object.entries(obj)) {
const propName = parent ? parent + '_' + key : key
const flattened: Record<string, unknown> = {}
if (value instanceof Date) {
flattened[key] = value.toISOString()
} else if(typeof value === 'object' && value !== null){
res = {...res, ...flatten(value as Record<string, unknown>, propName)}
} else {
res[propName] = value
}
}
return res
}
An example:
const example = {
person: {
firstName: 'Demo',
lastName: 'Person'
},
date: new Date(),
hello: 'world'
}
// becomes
const flattenedExample = {
person_firstName: 'Demo',
person_lastName: 'Person',
date: '2021-10-18T10:41:14.278Z',
hello: 'world'
}
Here is an actual oneliner of just 91 characters, using Underscore. (Of course. What else?)
var { reduce, isObject } = _;
var data = {
a: 1,
b: 2,
c: {
d: 3,
e: 4,
f: {
g: 5
},
h: 6
}
};
var tip = (v, m={}) => reduce(v, (m, v, k) => isObject(v) ? tip(v, m) : {...m, [k]: v}, m);
console.log(tip(data));
<script src="https://underscorejs.org/underscore-umd-min.js"></script>
Readable version:
var { reduce, isObject, extend } = _;
var data = {
a: 1,
b: 2,
c: {
d: 3,
e: 4,
f: {
g: 5
},
h: 6
}
};
// This function is passed to _.reduce below.
// We visit a single key of the input object. If the value
// itself is an object, we recursively copy its keys into
// the output object (memo) by calling tip. Otherwise we
// add the key-value pair to the output object directly.
function tipIteratee(memo, value, key) {
if (isObject(value)) return tip(value, memo);
return extend(memo, {[key]: value});
}
// The entry point of the algorithm. Walks over the keys of
// an object using _.reduce, collecting all tip keys in memo.
function tip(value, memo = {}) {
return _.reduce(value, tipIteratee, memo);
}
console.log(tip(data));
<script src="https://underscorejs.org/underscore-umd-min.js"></script>
Also works with Lodash.
To flatten only the first level of the object and merge duplicate object keys into an array:
var myObj = {
id: '123',
props: {
Name: 'Apple',
Type: 'Fruit',
Link: 'apple.com',
id: '345'
},
moreprops: {
id: "466"
}
};
const flattenObject = (obj) => {
let flat = {};
for (const [key, value] of Object.entries(obj)) {
if (typeof value === 'object' && value !== null) {
for (const [subkey, subvalue] of Object.entries(value)) {
// avoid overwriting duplicate keys: merge instead into array
typeof flat[subkey] === 'undefined' ?
flat[subkey] = subvalue :
Array.isArray(flat[subkey]) ?
flat[subkey].push(subvalue) :
flat[subkey] = [flat[subkey], subvalue]
}
} else {
flat = {...flat, ...{[key]: value}};
}
}
return flat;
}
console.log(flattenObject(myObj))
Object.assign requires a polyfill. This version is similar to previous ones, but it is not using Object.assign and it is still keep tracking of parent's name
const flatten = (obj, parent = null) => Object.keys(obj).reduce((acc, cur) =>
typeof obj[cur] === 'object' ? { ...acc, ...flatten(obj[cur], cur) } :
{ ...acc, [((parent) ? parent + '.' : "") + cur]: obj[cur] } , {})
const obj = {
a:2,
b: {
c:3
}
}
const flattened = flatten(obj)
console.log(flattened)
Here is a flatten function that correctly outputs array indexes.
function flatten(obj) {
const result = {};
for (const key of Object.keys(obj)) {
if (typeof obj[key] === 'object') {
const nested = flatten(obj[key]);
for (const nestedKey of Object.keys(nested)) {
result[`${key}.${nestedKey}`] = nested[nestedKey];
}
} else {
result[key] = obj[key];
}
}
return result;
}
Example Input:
{
"first_name": "validations.required",
"no_middle_name": "validations.required",
"last_name": "validations.required",
"dob": "validations.required",
"citizenship": "validations.required",
"citizenship_identity": {
"name": "validations.required",
"value": "validations.required"
},
"address": [
{
"country_code": "validations.required",
"street": "validations.required",
"city": "validations.required",
"state": "validations.required",
"zipcode": "validations.required",
"start_date": "validations.required",
"end_date": "validations.required"
},
{
"country_code": "validations.required",
"street": "validations.required",
"city": "validations.required",
"state": "validations.required",
"zipcode": "validations.required",
"start_date": "validations.required",
"end_date": "validations.required"
}
]
}
Example Output:
const flattenedOutput = flatten(inputObj);
{
"first_name": "validations.required",
"no_middle_name": "validations.required",
"last_name": "validations.required",
"dob": "validations.required",
"citizenship": "validations.required",
"citizenship_identity.name": "validations.required",
"citizenship_identity.value": "validations.required",
"address.0.country_code": "validations.required",
"address.0.street": "validations.required",
"address.0.city": "validations.required",
"address.0.state": "validations.required",
"address.0.zipcode": "validations.required",
"address.0.start_date": "validations.required",
"address.0.end_date": "validations.required",
"address.1.country_code": "validations.required",
"address.1.street": "validations.required",
"address.1.city": "validations.required",
"address.1.state": "validations.required",
"address.1.zipcode": "validations.required",
"address.1.start_date": "validations.required",
"address.1.end_date": "validations.required"
}
function flatten(obj: any) {
return Object.keys(obj).reduce((acc, current) => {
const key = `${current}`;
const currentValue = obj[current];
if (Array.isArray(currentValue) || Object(currentValue) === currentValue) {
Object.assign(acc, flatten(currentValue));
} else {
acc[key] = currentValue;
}
return acc;
}, {});
};
let obj = {
a:2,
b: {
c:3
}
}
console.log(flatten(obj))
Demo
https://stackblitz.com/edit/typescript-flatten-json
Here goes, not thoroughly tested. Utilizes ES6 syntax too!!
loopValues(val){
let vals = Object.values(val);
let q = [];
vals.forEach(elm => {
if(elm === null || elm === undefined) { return; }
if (typeof elm === 'object') {
q = [...q, ...this.loopValues(elm)];
}
return q.push(elm);
});
return q;
}
let flatValues = this.loopValues(object)
flatValues = flatValues.filter(elm => typeof elm !== 'object');
console.log(flatValues);
I know its been very long, but it may be helpful for some one in the future
I've used recursion
let resObj = {};
function flattenObj(obj) {
for (let key in obj) {
if (!(typeof obj[key] == 'object')) {
// console.log('not an object', key);
resObj[key] = obj[key];
// console.log('res obj is ', resObj);
} else {
flattenObj(obj[key]);
}
}
return resObj;
}
Here's my TypeScript extension from #Webber's answer. Also supports dates:
private flattenObject(obj: any): any {
const flattened = {};
for (const key of Object.keys(obj)) {
if (isNullOrUndefined(obj[key])) {
continue;
}
if (typeof obj[key].getMonth === 'function') {
flattened[key] = (obj[key] as Date).toISOString();
} else if (typeof obj[key] === 'object' && obj[key] !== null) {
Object.assign(flattened, this.flattenObject(obj[key]));
} else {
flattened[key] = obj[key];
}
}
return flattened;
}
const obj = {
a:2,
b: {
c:3
}
}
// recursive function for extracting keys
function extractKeys(obj) {
let flattenedObj = {};
for(let [key, value] of Object.entries(obj)){
if(typeof value === "object") {
flattenedObj = {...flattenedObj, ...extractKeys(value)};
} else {
flattenedObj[key] = value;
}
}
return flattenedObj;
}
// main code
let flattenedObj = extractKeys(obj);
console.log(flattenedObj);
I have a deeply nested structure in the javascript object without any arrays in it.
var data = {
bar: 'a',
child: {
b: 'b',
grand: {
greatgrand: {
c: 'c'
}
}
}
};
let arr = [];
const findParentGrandparent = (obj, target) => {
Object.entries(obj).forEach(child => {
if (typeof child[1] === 'object') {
findParentGrandparent(child[1]);
}
});
};
findParentGrandparent(data, 'c');
When I call the function with a target, I want to get the taget key itself, parent and grandparent.
For example, if the target is 'c', arr should become
['c', 'greatgrand', 'grand', 'child'];
if target is 'greatgrand', it should become
['greatgrand', 'grand', 'child'];
Thanks
I did it using your recursive pattern, you can change the way it handle errors also, here I throw if there is no result.
var data = {
bar: 'a',
child: {
b: 'b',
grand: {
greatgrand: {
c: 'c'
}
}
}
};
let arr = [];
const findParentGrandparent = (obj, target) => {
for (const child of Object.entries(obj)) {
if (typeof child[1] === 'object' && child[0] !== target) {
const result = findParentGrandparent(child[1], target);
return [...result, child[0]];
} else if (child[0] === target) {
return [child[0]];
}
};
throw new Error("not found"); // If it goes there the object is not found, you can throw or return a specific flag, as you wish.
};
console.log(findParentGrandparent(data, 'c'));
var data = {
bar: 'a',
child: {
b: 'b',
grand: {
greatgrand: {
c: 'c'
}
}
}
};
/**
* #param validate {boolean} = true - Pass true if need to check for existance of `target`
*/
const findParentGrandparent = (obj, target, validate = true) => {
let result = [];
for (let [key, value] of Object.entries(obj)) {
if (key === target) {
result.push(key);
break;
}
if (value.toString() === '[object Object]') {
result.push(key);
result = result.concat(findParentGrandparent(value, target, false))
}
}
if (validate && !result.includes(target)) {
return 'Not found';
}
return result;
};
let resultC = findParentGrandparent(data, 'c').reverse();
let resultGreatgrand = findParentGrandparent(data, 'greatgrand').reverse();
console.log('Result for "c":', resultC);
console.log('Result for "greatgrand":', resultGreatgrand);
You can use a recursive generator function:
function* get_vals(d, target, c = []){
for (var i of Object.keys(d)){
if (i === target){
yield [target, ...c.slice(0, 3)]
}
if (typeof d[i] === 'object'){
yield* get_vals(d[i], target, c = [i, ...c])
}
}
}
var result = get_vals(data, 'c').next().value
Output:
["c", "greatgrand", "grand", "child"]