One liner to flatten nested object

Javascriptunderscore.js

Javascript Problem Overview


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));

Javascript Solutions


Solution 1 - Javascript

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)
)

Solution 2 - Javascript

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

Solution 3 - Javascript

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!

Solution 4 - Javascript

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;	
}

Solution 5 - Javascript

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;
}

Solution 6 - Javascript

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;
}

Solution 7 - Javascript

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 output = crushObj(obj)
console.log(output)
// { a: 2, c: 3 }

Solution 8 - Javascript

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));
});

Solution 9 - Javascript

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.

Solution 10 - Javascript

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: https://stackoverflow.com/q/19098797/697154 - 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();

Solution 11 - Javascript

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))

Solution 12 - Javascript

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

Solution 13 - Javascript

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);

Solution 14 - Javascript

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;
}

Solution 15 - Javascript

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;
}

Solution 16 - Javascript

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'
}

Solution 17 - Javascript

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);

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questiondanday74View Question on Stackoverflow
Solution 1 - Javascriptuser663031View Answer on Stackoverflow
Solution 2 - JavascriptWebberView Answer on Stackoverflow
Solution 3 - JavascriptmuratgozelView Answer on Stackoverflow
Solution 4 - JavascriptJames BrierleyView Answer on Stackoverflow
Solution 5 - JavascriptMarco LackovicView Answer on Stackoverflow
Solution 6 - JavascriptNoobView Answer on Stackoverflow
Solution 7 - JavascriptmetaoryView Answer on Stackoverflow
Solution 8 - JavascriptpkfmView Answer on Stackoverflow
Solution 9 - JavascriptJulianView Answer on Stackoverflow
Solution 10 - JavascriptSk93View Answer on Stackoverflow
Solution 11 - JavascriptJohannesView Answer on Stackoverflow
Solution 12 - JavascriptHau LeView Answer on Stackoverflow
Solution 13 - JavascriptIkenna Anthony OkaforView Answer on Stackoverflow
Solution 14 - Javascriptdinesh dsvView Answer on Stackoverflow
Solution 15 - JavascriptRossView Answer on Stackoverflow
Solution 16 - JavascriptDennis AmelingView Answer on Stackoverflow
Solution 17 - JavascriptNeeraj GuptaView Answer on Stackoverflow