Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Flatten javascript objects into a single-depth object
var flattenObject = function(ob) {
var toReturn = {};
for (var i in ob) {
if (!ob.hasOwnProperty(i)) continue;
if ((typeof ob[i]) == 'object') {
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;
};
@jarseneault

This comment has been minimized.

Copy link

@jarseneault jarseneault commented Oct 4, 2013

Useful, thanks!

@r0busta

This comment has been minimized.

Copy link

@r0busta r0busta commented Oct 23, 2013

Many thanks for this snippet!

@whoughton

This comment has been minimized.

Copy link

@whoughton whoughton commented Feb 7, 2014

line 7 will flatten an Array (typeof [] => "object"), you may want to use toString.call(ob[i]) instead if you want the actual array, instead of it being flattened too.

@Echooff3

This comment has been minimized.

Copy link

@Echooff3 Echooff3 commented Mar 12, 2014

Thanks! Just what I was looking for!

@trey

This comment has been minimized.

Copy link

@trey trey commented Mar 17, 2014

👍

@dustinlarimer

This comment has been minimized.

Copy link

@dustinlarimer dustinlarimer commented May 15, 2014

This is awesome, nice work!

I ended up making one small mod on line 7 to flatten keys carrying null values:

  if ((typeof ob[i]) == 'object' && ob[i] !== null) {

(since typeof null == "object")

@wricardo

This comment has been minimized.

Copy link

@wricardo wricardo commented Oct 7, 2014

Nice! Just what I was looking for.

@mcnasby

This comment has been minimized.

Copy link

@mcnasby mcnasby commented Nov 24, 2014

Super helpful. Thank you!

@ckahle33

This comment has been minimized.

Copy link

@ckahle33 ckahle33 commented Dec 11, 2014

many thanks sir

@aclave1

This comment has been minimized.

Copy link

@aclave1 aclave1 commented Dec 14, 2014

thank you sir. 👍

@gderosa

This comment has been minimized.

Copy link

@gderosa gderosa commented Dec 17, 2014

Definitely agree with @dustinlarimer. It's also generally good practice to use === rather than ==. That being said, awesome 😊

@gdibble

This comment has been minimized.

Copy link

@gdibble gdibble commented Jan 27, 2015

pure awesomesauce :: ty


i forked @ https://gist.github.com/gdibble/9e0f34f0bb8a9cf2be43 w/ this behavior:

  • input: { 'a':{ 'b':{ 'b2':2 }, 'c':{ 'c2':2, 'c3':3 } } }
  • output: { 'a.b.b2':2, 'a.c.c2':2, 'a.c.c3':3 }
@askhogan

This comment has been minimized.

Copy link

@askhogan askhogan commented Jun 7, 2015

This only goes one level deep. This doesn't flatten as expected

@dsernst

This comment has been minimized.

Copy link

@dsernst dsernst commented Jul 23, 2015

This silently fails for Date properties, because they pass the typeof i === 'object' test, but can't actually be recursively traversed.

@Djuka

This comment has been minimized.

Copy link

@Djuka Djuka commented Aug 11, 2015

If you want arrays and date objects to be ignored you should use:

OK way to do it (if you know you will only go across arrays, objects and Date objects):

    Object.prototype.toString.call( obj[i] ) === '[object Object]'

    // Because
    Object.prototype.toString.call( [] ) // returns "[object Array]"
    Object.prototype.toString.call( new Date() ) // returns "[object Date]"    
    Object.prototype.toString.call( {} ) // returns "[object Object]"

Better way (if you need to differentiate by type more than just arrays, objects and Date object):

    var obj = {};
    obj.constructor === Object; // will return true

    // So if we have something like
    function Test() {}
    var obj = new Test();
    obj.constructor === Test // will return true
    // Sweet! We can detect if it is an object with custom constructor

    // Whereas
    Object.prototype.toString.call( obj ) === "[object Object]" // will return true
   // which is just not OK if there are object with custom constructor functions
@cesc1989

This comment has been minimized.

Copy link

@cesc1989 cesc1989 commented Nov 24, 2015

Many thanks for this cool snippet.

@brycepj

This comment has been minimized.

Copy link

@brycepj brycepj commented Jan 12, 2016

Beautiful!!!

@360disrupt

This comment has been minimized.

Copy link

@360disrupt 360disrupt commented Oct 13, 2016

Modified version in coffee:

flattenObject = (ob) ->
  what = Object.prototype.toString
  toReturn = {}
  for i of ob
    if !ob.hasOwnProperty(i)
      continue
    result = what.call(ob[i])
    if result == '[object Object]' or result == '[object Array]'
      flatObject = flattenObject(ob[i])
      for x of flatObject
        if !flatObject.hasOwnProperty(x)
          continue
        toReturn[i + '.' + x] = flatObject[x]
    else
      toReturn[i] = ob[i]
  toReturn
@optyler

This comment has been minimized.

Copy link

@optyler optyler commented Nov 10, 2016

as the documentation says :

The Object.keys() method returns an array of a given object's own enumerable properties, in the same order as that provided by a for...in loop (the difference being that a for-in loop enumerates properties in the prototype chain as well).

Then I use it to write another version of the method without unnecessary hasOwnProperty checks, it works with String, int, object, array, array of object, null, undefined, you can check this jsbin :

var flattenObject = function(ob) {
  
  return Object.keys(ob).reduce(function(toReturn, k) {

    if (Object.prototype.toString.call(ob[k]) === '[object Date]') {
      toReturn[k] = ob[k].toString();
    }
    else if ((typeof ob[k]) === 'object' && ob[k]) {
      var flatObject = flattenObject(ob[k]);
      Object.keys(flatObject).forEach(function(k2) {
        toReturn[k + '.' + k2] = flatObject[k2];
      });
    }
    else {
      toReturn[k] = ob[k];
    }

    return toReturn;
  }, {});
};
@mattwelke

This comment has been minimized.

Copy link

@mattwelke mattwelke commented Jan 19, 2017

Might I recommend updating this to ES6?

@dominikwilkowski

This comment has been minimized.

Copy link

@dominikwilkowski dominikwilkowski commented Feb 22, 2017

I created this as an ES6 closure:

/**
 * PRIVATE
 * Flatten a deep object into a one level object with it’s path as key
 *
 * @param  {object} object - The object to be flattened
 *
 * @return {object}        - The resulting flat object
 */
const flatten = object => {
  return Object.assign( {}, ...function _flatten( objectBit, path = '' ) {  //spread the result into our return object
    return [].concat(                                                       //concat everything into one level
      ...Object.keys( objectBit ).map(                                      //iterate over object
        key => typeof objectBit[ key ] === 'object' ?                       //check if there is a nested object
          _flatten( objectBit[ key ], `${ path }/${ key }` ) :              //call itself if there is
          ( { [ `${ path }/${ key }` ]: objectBit[ key ] } )                //append object with it’s path as key
      )
    )
  }( object ) );
};

Call it via:

const flat = flatten( realDeepObject );

Test case:

const realDeepObject = {
  level1: {
    level2: {
      level3: {
        more: 'stuff', //duplicate key
        other: 'stuff',
        level4: {
          the: 'end',
        },
      },
    },
    level2still: {
      last: 'one',
    },
    am: 'bored',
  },
  more: 'stuff', //duplicate key
  ipsum: {
    lorem: 'latin',
  },
};

const flat = flatten( realDeepObject );

console.log( flat );

Output:

{ '/level1/level2/level3/more': 'stuff',
  '/level1/level2/level3/other': 'stuff',
  '/level1/level2/level3/level4/the': 'end',
  '/level1/level2still/last': 'one',
  '/level1/am': 'bored',
  '/more': 'stuff',
  '/ipsum/lorem': 'latin' }

(You can replace the separators / with a dot . to make it fit your job)

@afrozl

This comment has been minimized.

Copy link

@afrozl afrozl commented Mar 3, 2017

what would I have to change so that the first separator is removed?
output should be:

{ 'level1/level2/level3/more': 'stuff',
'level1/level2/level3/other': 'stuff',
'level1/level2/level3/level4/the': 'end',
'level1/level2still/last': 'one',
'level1/am': 'bored',
'more': 'stuff',
'ipsum/lorem': 'latin' }

@prtkkmrsngh

This comment has been minimized.

Copy link

@prtkkmrsngh prtkkmrsngh commented Apr 22, 2017

@afrozl you can modify it like this _flatten( objectBit, path = 'deepObject' )

@tabareh

This comment has been minimized.

Copy link

@tabareh tabareh commented May 3, 2017

Tyepscript version

private flattenObject(ob:any): any {
        const toReturn = {};

        for (const key in ob) {
            if (!ob.hasOwnProperty(key)) {
                continue;
            }
            if ((typeof ob[key]) === 'object') {
                const flatObject = this.flattenObject(ob[key]);
                for (const key2 in flatObject) {
                    if (!flatObject.hasOwnProperty(key2)) {
                        continue;
                    }
                    // this.logger.debug(`adding ${key + '.' + key2}:${flatObject[key2]}`);
                    toReturn[key + '.' + key2] = flatObject[key2];
                }
            } else {
                // this.logger.debug(`adding ${key}:${ob[key]}`);
                toReturn[key] = ob[key];
            }
        }
        return toReturn;
    };
@danieldietrich

This comment has been minimized.

Copy link

@danieldietrich danieldietrich commented May 4, 2017

Hi, I've changed the version of @dominikwilkowski slightly:

function flatten(object, separator = '.') {
    return Object.assign({}, ...function _flatten(child, path = []) {
        return [].concat(...Object.keys(child).map(key => typeof child[key] === 'object'
            ? _flatten(child[key], path.concat([key]))
            : ({ [path.concat([key]).join(separator)] : child[key] })
        ));
    }(object));
}
@misterbrownlee

This comment has been minimized.

Copy link

@misterbrownlee misterbrownlee commented May 18, 2017

👍 Thanks, @danieldietrich

@JimiC

This comment has been minimized.

Copy link

@JimiC JimiC commented Jun 10, 2017

Here is an improved version of @danieldietrich in typescript but can easily be transformed to ES6

function flatten(object: object, separator = '.'): object {
  const isValidObject = (value): boolean => {
    if (!value) { return false; }
    const isArray = Array.isArray(value);
    const isBuffer = Buffer.isBuffer(value);
    const isΟbject = Object.prototype.toString.call(value) === "[object Object]";
    const hasKeys = !!Object.keys(value).length;
    return !isArray && !isBuffer && isΟbject && hasKeys;
  };
  return Object.assign({}, ...function _flatten(child, path = []) {
    return [].concat(...Object.keys(child)
      .map(key => isValidObject(child[key])
        ? _flatten(child[key], path.concat([key]))
        : { [path.concat([key]).join(separator)]: child[key] }));
  }(object));
}
@jibsaramnim

This comment has been minimized.

Copy link

@jibsaramnim jibsaramnim commented Jun 23, 2017

@JimiC I had some errors and when using your version, regarding implicit any returns and the string[] to never[] thing. I have tweaked it but as I'm still getting familiar with TypeScript I am unsure if I have actually improved it or merely found a way for the compiler to give up complaining. Sharing it here just in case it's helpful:

export function flatten(object: Object, separator: string = '.'): Object {
    const isValidObject = (value: {}): boolean => {
        if (!value) { 
            return false;
        }

        const isArray = Array.isArray(value);
        const isBuffer = Buffer.isBuffer(value);
        const isΟbject = Object.prototype.toString.call(value) === '[object Object]';
        const hasKeys = !!Object.keys(value).length;

        return !isArray && !isBuffer && isΟbject && hasKeys;
    };

    const walker = (child: {}, path: Array<string> = []): Object => {
        return Object.assign({}, ...Object.keys(child).map(key => isValidObject(child[key])
            ? walker(child[key], path.concat([key]))
            : { [path.concat([key]).join(separator)]: child[key] })
        );
    };

    return Object.assign({}, walker(object));
}
@tonioriol

This comment has been minimized.

Copy link

@tonioriol tonioriol commented Aug 5, 2017

ES6 version of @hellodeibu, had to remove the isBuffer check, since there's no Buffer class in ES6.

function flatten(object, separator = '.') {

	const isValidObject = value => {
		if (!value) {
			return false
		}

		const isArray  = Array.isArray(value)
		const isObject = Object.prototype.toString.call(value) === '[object Object]'
		const hasKeys  = !!Object.keys(value).length

		return !isArray && isObject && hasKeys
	}

	const walker = (child, path = []) => {

		return Object.assign({}, ...Object.keys(child).map(key => isValidObject(child[key])
			? walker(child[key], path.concat([key]))
			: { [path.concat([key]).join(separator)] : child[key] })
		)
	}

	return Object.assign({}, walker(object))
}
@AndreiCalazans

This comment has been minimized.

Copy link

@AndreiCalazans AndreiCalazans commented Aug 12, 2017

thanks @tonioriol it works great.

@iks21

This comment has been minimized.

Copy link

@iks21 iks21 commented Aug 12, 2017

@danieldietrich I like your version the most, but typeof child[key] === 'object' && child[key] !== null would be better as for me, because for now function works only if there are no null values in object

@xv1t

This comment has been minimized.

Copy link

@xv1t xv1t commented Sep 19, 2017

From this post I'm creates little utils Flatt
https://github.com/xv1t/flatt
Thanx

@iCoderXXI

This comment has been minimized.

Copy link

@iCoderXXI iCoderXXI commented Sep 28, 2017

const flattenObj = (o, pk='') => {
  const ret = Object.keys(o).reduce((a, key) => {
    const kk = ''+(pk?pk+':'+key:key);
    if (typeof o[key] !== 'object') {
      a[kk] = o[key];
    } else {
      a = Object.assign(a, flattenObj(o[key], kk));
    }
    return a;
  }, {})
  return ret;
}
@pablohpsilva

This comment has been minimized.

Copy link

@pablohpsilva pablohpsilva commented Oct 7, 2017

const flattenObj = (obj) => {
  if (!obj) {
    throw Error(`flattenObj function expects an Object, received ${typeof obj}`)
  }
  return Object.keys(obj).reduce((acc, curr) => {
    const objValue = obj[curr]
    const ret = (objValue && objValue instanceof Object)
      ? flattenObj(objValue)
      : { [curr]: objValue }
    return Object.assign(acc, ret)
  }, {})
}
@ivan-kleshnin

This comment has been minimized.

Copy link

@ivan-kleshnin ivan-kleshnin commented Oct 9, 2017

@pablohpsilva – your code is fundamentally wrong. Object flattening should accumulate keys:

console.log(flattenObj({
  users: {
    set: "whatnot",
  },
  projects: {
    set: "whatnot",
  }
}))

has to produce:

{
 "users.set": "whatnot",
 "projects.set": "whatnot",
}

Otherwise it's pretty worthless (not to mention name clashes...). Also flattening must not flatten dates, regexps, arrays, etc.

@ivan-kleshnin

This comment has been minimized.

Copy link

@ivan-kleshnin ivan-kleshnin commented Oct 9, 2017

Enjoy:

let isPlainObj = (o) => Boolean(
  o && o.constructor && o.constructor.prototype && o.constructor.prototype.hasOwnProperty("isPrototypeOf")
)

let flattenObj = (obj, keys=[]) => {
  return Object.keys(obj).reduce((acc, key) => {
    return Object.assign(acc, isPlainObj(obj[key])
      ? flattenObj(obj[key], keys.concat(key))
      : {[keys.concat(key).join(".")]: obj[key]}
    )
  }, {})
}
console.log(flattenObj({}) // {}
console.log(flattenObj({foo: "foo"}) // {foo: "foo"}
console.log(flattenObj({
  users: {
    set: "whatnot",
  },
  projects: {
    set: "whatnot",
  },
  dates: {
    d1: new Date(),
    d2: new Date(),
  },
  array: [{foo: "foo"}, {bar: "bar"}]
}))

{ 'users.set': 'whatnot',
  'projects.set': 'whatnot',
  'dates.d1': <dateobj1>,
  'dates.d2': <dateobj2>,
  array: [ { foo: 'foo' }, { bar: 'bar' } ] }
@UnitOneOnline

This comment has been minimized.

Copy link

@UnitOneOnline UnitOneOnline commented Oct 24, 2017

this version will return a flattened object which can be used to generate rails style params which will be interpreted to a ruby object. example output goes {a: {b: 'b', x: { y: 'y'}}, c: 'c', d: { e: 'e'}} => { 'a[b]': 'b', 'a[x][y]': 'y', 'c': 'c', 'd[e]': 'e' }. this can be useful if you would like to use rails as an api server from javascript / ajax.

  // ussage : 
  // flattenobj( object );   // 'key' and 'q' below are internal parameters only. do not use.
 function flattenobj(obj, key = '', q = {}) {

   for (k in obj) {

     if (typeof (obj) === "object") {

       let kb;

        key === '' ? kb = k : kb = '[' + k + ']';

        flattenobj(obj[k], key + kb, q);

      } else {

         q[key] = obj;

     }
   }
 return q;
 }
@MidnightDesign

This comment has been minimized.

Copy link

@MidnightDesign MidnightDesign commented Feb 1, 2018

TypeScript version with custom keys:

type KeyFactory = (previousKey: string, currentKey: string) => string;

function flattenObject(ob: { [key: string]: any }, keyFactory: KeyFactory | null = null): { [key: string]: any } {
    if (keyFactory === null) {
        keyFactory = (previousKey, currentKey) => previousKey + '.' + currentKey;
    }
    const toReturn: { [key: string]: string } = {};
    for (const i in ob) {
        if (!ob.hasOwnProperty(i)) {
            continue;
        }
        if ((typeof ob[i]) === 'object') {
            const flatObject = flattenObject(ob[i]);
            for (const x in flatObject) {
                if (!flatObject.hasOwnProperty(x)) {
                    continue;
                }
                toReturn[keyFactory(i, x)] = flatObject[x];
            }
        } else {
            toReturn[i] = ob[i];
        }
    }
    return toReturn;
}

Usage:

const obj = { foo: { bar: 'baz' } };
flattenObject(obj, (previous, current) => `${previous}[${current}]`)
// Returns { 'foo[bar]': 'baz' }
@lfreneda

This comment has been minimized.

Copy link

@lfreneda lfreneda commented Feb 28, 2018

What is the lasted version? 🤔

@matheusMFCosta

This comment has been minimized.

Copy link

@matheusMFCosta matheusMFCosta commented Mar 14, 2018

last version

const flatten = (object, prefix = '') => {
  return Object.keys(object).reduce((prev, element) => {
    return object[element]  && typeof object[element] == 'object' &&  !Array.isArray(element)
      ? { ...prev, ...flatten(object[element], `${prefix}${element}.`) }
      : { ...prev, ...{ [`${prefix}${element}`]: object[element] } }
  }, {})
}

Usage

const obj = { foo: { bar: 'baz' } };
flatten(obj)
// Return {foo.bar: "baz"}
@ruisebastiao

This comment has been minimized.

Copy link

@ruisebastiao ruisebastiao commented Mar 27, 2018

in node version 9.x i got the error:

? { ...prev, ...flatten(object[element], `${prefix}${element}.`) }
          ^^^

SyntaxError: Unexpected token ...
@subhranshudas

This comment has been minimized.

Copy link

@subhranshudas subhranshudas commented Apr 12, 2018

@tonioriol and others any body knows how to transform back the flattened object to the original object. Lets say i change the values of the 'x.y.z' keys in the flattened object, now i want to get the original object structure with the new values in them.

@arnigudj

This comment has been minimized.

Copy link

@arnigudj arnigudj commented Jun 8, 2018

I used @ruisebastiao and fixed few minor things

const flatten = (object, prefix = '') =>
  Object.keys(object).reduce(
    (prev, element) =>
      object[element] &&
      typeof object[element] === 'object' &&
      !Array.isArray(object[element])
        ? { ...prev, ...flatten(object[element], `${prefix}${element}.`) }
        : { ...prev, ...{ [`${prefix}${element}`]: object[element] } },
    {},
  );
@pozylon

This comment has been minimized.

Copy link

@pozylon pozylon commented Aug 30, 2018

I used @arnigudj and added nested array flattening:

const flatten = (objectOrArray, prefix = '') => {
  const nestElement = (prev, value, key) => (value
          && typeof value === 'object'
    ? { ...prev, ...flatten(value, `${prefix}${key}.`) }
    : { ...prev, ...{ [`${prefix}${key}`]: value } });

  return Array.isArray(objectOrArray)
    ? objectOrArray.reduce(nestElement, {})
    : Object.keys(objectOrArray).reduce(
      (prev, element) => nestElement(prev, objectOrArray[element], element),
      {},
    );
};
@andyjuramlee

This comment has been minimized.

Copy link

@andyjuramlee andyjuramlee commented Oct 24, 2018

Thank you!

@edwardEvans094

This comment has been minimized.

Copy link

@edwardEvans094 edwardEvans094 commented Oct 29, 2018

anyone can revert this, i want to create object from single-depth object

@mehrjoo

This comment has been minimized.

Copy link

@mehrjoo mehrjoo commented Nov 15, 2018

I used @pozylon and added a formatter function, which will be applied on each key.

const flatten = (objectOrArray, prefix = '', formatter = (k) => ('.' + k)) => {
  const nestElement = (prev, value, key) => (
    (value && typeof value === 'object')
      ? { ...prev, ...flatten(value, `${prefix}${formatter(key)}`, formatter) }
      : { ...prev, ...{ [`${prefix}${formatter(key)}`]: value } });

  return Array.isArray(objectOrArray)
    ? objectOrArray.reduce(nestElement, {})
    : Object.keys(objectOrArray).reduce(
      (prev, element) => nestElement(prev, objectOrArray[element], element),
      {},
    );
};
@guillim

This comment has been minimized.

Copy link

@guillim guillim commented Nov 30, 2018

Same thing as @mehrjoo, simply allows the possibility to have different formatter for the level 0 keys of your object

const flatten = (objectOrArray, prefix = '', formatter = (k) => (k)) => {
  const nestedFormatter = (k) => ('.' + k)
  const nestElement = (prev, value, key) => (
    (value && typeof value === 'object')
      ? { ...prev, ...flatten(value, `${prefix}${formatter(key)}`, nestedFormatter) }
      : { ...prev, ...{ [`${prefix}${formatter(key)}`]: value } });

  return Array.isArray(objectOrArray)
    ? objectOrArray.reduce(nestElement, {})
    : Object.keys(objectOrArray).reduce(
      (prev, element) => nestElement(prev, objectOrArray[element], element),
      {},
    );
};

This way for :
input = { 'a':{ 'b':{ 'b2':2 }, 'c':{ 'c2':2, 'c3':3 } } }

You have this result :
{ a.b.b2: 2, a.c.c2: 2, a.c.c3: 3 }

While with @mehrjoo you have this :
{ .a.b.b2: 2, .a.c.c2: 2, .a.c.c3: 3 }

@jgcoding

This comment has been minimized.

Copy link

@jgcoding jgcoding commented Dec 17, 2018

very nicely done! Thank you for this!

@paheld

This comment has been minimized.

Copy link

@paheld paheld commented Jan 29, 2019

anyone can revert this, i want to create object from single-depth object

@edwardEvans094 I found code to reverse this at https://stackoverflow.com/questions/42694980/how-to-unflatten-a-javascript-object-in-a-daisy-chain-dot-notation-into-an-objec

@jaumzors

This comment has been minimized.

Copy link

@jaumzors jaumzors commented Feb 20, 2019

Really useful, thank you!

@MacGyver27

This comment has been minimized.

Copy link

@MacGyver27 MacGyver27 commented Mar 15, 2019

anybody has an opposite function? to unflatten it?

@x47188

This comment has been minimized.

Copy link

@x47188 x47188 commented Jan 20, 2020

A Typescript 3.7+ version:

export function flatten<T extends Record<string, any>>(
  object: T,
  path: string | null = null,
  separator = '.'
): T {
  return Object.keys(object).reduce((acc: T, key: string): T => {
    const newPath = [path, key].filter(Boolean).join(separator);
    return typeof object?.[key] === 'object'
      ? { ...acc, ...flatten(object[key], newPath, separator) }
      : { ...acc, [newPath]: object[key] };
  }, {} as T);
}
@gitty-git-git

This comment has been minimized.

Copy link

@gitty-git-git gitty-git-git commented Mar 21, 2020

@guillim
How do I remove the numbers from the keys in an array of objects? I have this fiddle. This data:

var data = {
  "ticker": "AAPL",
  "name": "Apple",
  "data": [
    1,2,3,4
  ],
  "obj": [
    {"revenue": 1, "income": 11},
    {"revenue": 2, "income": 22},
  ],
}

Results in:
["ticker", "name", "data.0", "data.1", "data.2", "data.3", "obj.0.revenue", "obj.0.income", "obj.1.revenue", "obj.1.income"]

I'd like:
["ticker", "name", "data", "data", "data", "data", "obj.revenue", "obj.income", "obj.revenue", "obj.income"]

@codeBelt

This comment has been minimized.

Copy link

@codeBelt codeBelt commented Jul 26, 2020

What an awesome thread. Thanks to everyone here!

I made an improvement from @x47188 version to handle null values and empty [] arrays. Also added the Date and Regex object for the hell of it.

export function flatten<T extends Record<string, any>>(
  object: T,
  path: string | null = null,
  separator = '.'
): T {
  return Object.keys(object).reduce((acc: T, key: string): T => {
    const value = object[key];

    const newPath = [path, key].filter(Boolean).join(separator);

    const isObject = [
      typeof value === 'object',
      value !== null,
      !(value instanceof Date),
      !(value instanceof RegExp),
      !(Array.isArray(value) && value.length === 0),
    ].every(Boolean);

    return isObject
      ? { ...acc, ...flatten(value, newPath, separator) }
      : { ...acc, [newPath]: value };
  }, {} as T);
}

Example: https://stackblitz.com/edit/typescript-shdjoq

@lveillard

This comment has been minimized.

Copy link

@lveillard lveillard commented Aug 7, 2020

I'm wondering how to do it similarly to what @gitty-git-git asks.
But i want to keep arrays as they are unless they have objects, in that case flatten the objects.
For example this:

flatten({a:{b:2},c:[{e:{f:4}}]})

{
  "a.b": 2,
  "c": [
    {
      "e.f": 4
    }
  ]
}

Anyone has an idea? @gitty-git-git did you find a way to do yours?

@danzelbel

This comment has been minimized.

Copy link

@danzelbel danzelbel commented Sep 10, 2020

slighlty modified from @codeBelt to output array indices as [0] instead of as property .0

export function flatten<T extends Record<string, any>>(object: T, path: string | null = null, separator = '.'): T {
  return Object.keys(object).reduce((acc: T, key: string): T => {
    const value = object[key];
    const newPath = Array.isArray(object)
      ? `${path ? path : ''}[${key}]`
      : [path, key].filter(Boolean).join(separator);
    const isObject = [
      typeof value === 'object',
      value !== null,
      !(value instanceof Date),
      !(value instanceof RegExp),
      !(Array.isArray(value) && value.length === 0),
    ].every(Boolean);

    return isObject
      ? { ...acc, ...flatten(value, newPath, separator) }
      : { ...acc, [newPath]: value };
  }, {} as T);
}

Forked example: https://stackblitz.com/edit/typescript-pwsl83

@RocktimSaikia

This comment has been minimized.

Copy link

@RocktimSaikia RocktimSaikia commented Oct 5, 2020

I have put together a simple module, Flatify-obj based on this original gist with some additional tweaks and tests.

Usage

   const flattenObject = require('flatify-obj');

   flattenObject({foo: {bar: {unicorn: '🦄'}}})
   //=> { 'foo.bar.unicorn': '🦄' }

   flattenObject({foo: {unicorn: '🦄'}, bar: 'unicorn'}, {onlyLeaves: true});
   //=> {unicorn: '🦄', bar: 'unicorn'}

For additional features PRs are welcome 🦄

@tibdex

This comment has been minimized.

Copy link

@tibdex tibdex commented Nov 17, 2020

Another one written in TypeScript: tree-to-flat-map.

@a201150209

This comment has been minimized.

Copy link

@a201150209 a201150209 commented Nov 18, 2020

you are the best!

@contributorpw

This comment has been minimized.

Copy link

@contributorpw contributorpw commented Nov 26, 2020

@danzelbel like a charm!

@ludob78

This comment has been minimized.

Copy link

@ludob78 ludob78 commented Dec 7, 2020

Jewel!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment