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