Skip to content

Instantly share code, notes, and snippets.

@penguinboy
Created January 2, 2011 01:55
Show Gist options
  • Save penguinboy/762197 to your computer and use it in GitHub Desktop.
Save penguinboy/762197 to your computer and use it in GitHub Desktop.
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;
};
@ivan-kleshnin
Copy link

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
Copy link

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
Copy link

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
Copy link

What is the lasted version? 🤔

@matheusMFCosta
Copy link

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
Copy link

in node version 9.x i got the error:

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

SyntaxError: Unexpected token ...

@subhranshudas
Copy link

@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
Copy link

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
Copy link

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
Copy link

Thank you!

@toniqhz
Copy link

toniqhz commented Oct 29, 2018

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

@mehrjoo
Copy link

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
Copy link

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
Copy link

very nicely done! Thank you for this!

@paheld
Copy link

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
Copy link

Really useful, thank you!

@aronmgv
Copy link

aronmgv commented Mar 15, 2019

anybody has an opposite function? to unflatten it?

@x47188
Copy link

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
Copy link

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
Copy link

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
Copy link

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
Copy link

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
Copy link

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
Copy link

tibdex commented Nov 17, 2020

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

@a201150209
Copy link

you are the best!

@oshliaer
Copy link

@danzelbel like a charm!

@ludob78
Copy link

ludob78 commented Dec 7, 2020

Jewel!

@wowjeeez
Copy link

wowjeeez commented May 22, 2021

This is my implementation, it just flattens every object in the parent object into one, without keying them with object.key.
Working example
Code:

function flatten(obj = {}) {
  const doneObject = {}
  for (const [k, v] of Object.entries(obj)) {
    if (typeof v == "object" && !(v instanceof  Date) && !Array.isArray(v) && !(v instanceof regExp)) {
       Object.assign(doneObject, flatten(v))
    } else {
      doneObject[k] = v
    }
  }
  return doneObject
}

@jcv-pt
Copy link

jcv-pt commented Aug 26, 2021

Thank you for the original implementation!

@KuSh
Copy link

KuSh commented Feb 9, 2022

A version that leaves undefined behind, array as is and don't try to flatten primitive objects (Number, Boolean, BigInt and String essentially) :

const flatten = <T extends Record<string, any>>(object: T, path?: string): Record<string, any> =>
  Object.entries(object).reduce((acc, [key, val]) => {
    if (val === undefined) return acc;
    if (path) key = `${path}.${key}`;
    if (typeof val === 'object' && val !== null && !(val instanceof Date) && !(val instanceof RegExp) && !Array.isArray(val)) {
      if (val !== val.valueOf()) {
        return { ...acc, [key]: val.valueOf() };
      }
      return { ...acc, ...flatten(val, key) };
    }
    return { ...acc, [key]: val };
  }, {});

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