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