Skip to content

Instantly share code, notes, and snippets.

@penguinboy
Created January 2, 2011 01:55
Show Gist options
  • Select an option

  • Save penguinboy/762197 to your computer and use it in GitHub Desktop.

Select an option

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

ivan-kleshnin commented Oct 9, 2017

Copy link
Copy Markdown

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

UnitOneOnline commented Oct 24, 2017

Copy link
Copy Markdown

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

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

What is the lasted version? 🤔

@matheusMFCosta

matheusMFCosta commented Mar 14, 2018

Copy link
Copy Markdown

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

in node version 9.x i got the error:

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

SyntaxError: Unexpected token ...

@subhranshudas

Copy link
Copy Markdown

@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

arnigudj commented Jun 8, 2018

Copy link
Copy Markdown

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

pozylon commented Aug 30, 2018

Copy link
Copy Markdown

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

Thank you!

@toniqhz

toniqhz commented Oct 29, 2018

Copy link
Copy Markdown

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

@mehrjoo

mehrjoo commented Nov 15, 2018

Copy link
Copy Markdown

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

guillim commented Nov 30, 2018

Copy link
Copy Markdown

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 }

@StackBlaster

Copy link
Copy Markdown

very nicely done! Thank you for this!

@paheld

paheld commented Jan 29, 2019

Copy link
Copy Markdown

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

@jaumplopes

Copy link
Copy Markdown

Really useful, thank you!

@aronmgv

aronmgv commented Mar 15, 2019

Copy link
Copy Markdown

anybody has an opposite function? to unflatten it?

ghost commented Jan 20, 2020

Copy link
Copy Markdown

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

gitty-git-git commented Mar 21, 2020

Copy link
Copy Markdown

@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

codeBelt commented Jul 26, 2020

Copy link
Copy Markdown

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

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

danzelbel commented Sep 10, 2020

Copy link
Copy Markdown

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

rocktimsaikia commented Oct 5, 2020

Copy link
Copy Markdown

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

tibdex commented Nov 17, 2020

Copy link
Copy Markdown

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

@a201150209

Copy link
Copy Markdown

you are the best!

@oshliaer

Copy link
Copy Markdown

@danzelbel like a charm!

@ludob78

ludob78 commented Dec 7, 2020

Copy link
Copy Markdown

Jewel!

@wowjeeez

wowjeeez commented May 22, 2021

Copy link
Copy Markdown

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

jcv-pt commented Aug 26, 2021

Copy link
Copy Markdown

Thank you for the original implementation!

@KuSh

KuSh commented Feb 9, 2022

Copy link
Copy Markdown

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