ReactHustle

How to Flatten an Object in JavaScript or TypeScript

Jasser Mark Arioste

Jasser Mark Arioste

How to Flatten an Object in JavaScript or TypeScript

Hello, hustlers! In this article, you'll learn how to flatten objects in JavaScript and Typescript. You will also learn other variations depending on the browser support.

Flatten this object:

const obj = {
  slug: 'javascript-tutorial',
  content: {
    title: 'Javascript Tutorial',
    body: 'Hello, world.',
  },
  stats: {
    views: 100,
    shares: 200,
    otherStat: {
      test: 1,
    },
  },
  tags: ['javascript', 'typescrpt', 'nextjs'],
};
123456789101112131415

Into this:

{
    "slug": "javascript-tutorial",
    "content.title": "Javascript Tutorial",
    "content.body": "Hello, world.",
    "stats.views": 100,
    "stats.shares": 200,
    "stats.otherStat.test": 1,
    "tags.0": "javascript",
    "tags.1": "typescript",
    "tags.2": "nextjs"
}
1234567891011

This transforms the keys into the dot notation used by MongoDB.

Why would you need to flatten an object? #

Flattening an object is beneficial in a lot of scenarios. It allows you to manipulate the object more easily. It also allows you to easily map through all the properties. 

I'm not sure about other databases, but if you're using MongoDB as your database, it allows you to safely update your data using the $set operator.

It's also useful if you have a GraphQL server and you only want to project the queried fields to save some bandwidth.

How to flatten an object in TypeScript? #

Since a nested object is a tree data structure, the best way to go through all the nested properties is to use recursion.

Here is the code and it uses a modern es6 implementation, I'll explain it afterwards:

// flattenObject.ts
export const flattenObject = (obj:Object, parentKey?:string) => {
  let result = {};

  Object.keys(obj).forEach((key) => {
    const value = obj[key];
    const _key = parentKey ? parentKey + '.' + key : key;
    if (typeof value === 'object') {
      result = { ...result, ...flattenObject(value, _key) };
    } else {
      result[_key] = value;
    }
    console.log(`parentKey: "${parentKey}", _key: "${_key}"`);
  });

  return result;
};
1234567891011121314151617

Explanation:

  1. Line #2-3,14, We declare a function flattenObject(obj:Object, parentKey?: string) that returns the result object.
  2. Line #4: We use Object.keys() to loop through all the keys of obj
  3. Line #5: We store the value to a variable for more readability.
  4. Line #6: We store the key, if there's a parentKey, we prepend the parentKey to the current key. For example  if parentKey="stats" and current key="views", this becomes "stats.views"
  5. Line #7-8: If the property is an object, we call flattenObject recursively to get a flattened version of the child object. We assign all the properties of the child object to the result object using the spread operator. We also pass the calculated _key, to retain the path of its ancestor objects. 
  6. Line 10: If the property is not an Object, we store the value using the calculated _key. If this is the root object, then it would not have a parent key.
  7. Line 13: A console.log to see the result for each iteration. This is to show you how each property is traversed.
  8. Line 14: Once we finished traversing through all the properties, we return the result.

If you check the logs the result will be this.

parentKey: "undefined", _key: "slug"
parentKey: "content", _key: "content.title"
parentKey: "content", _key: "content.body"
parentKey: "undefined", _key: "content"
parentKey: "stats", _key: "stats.views"
parentKey: "stats", _key: "stats.shares"
parentKey: "stats.otherStat", _key: "stats.otherStat.test"
parentKey: "stats", _key: "stats.otherStat"
parentKey: "undefined", _key: "stats"
parentKey: "tags", _key: "tags.0"
parentKey: "tags", _key: "tags.1"
parentKey: "tags", _key: "tags.2"
parentKey: "undefined", _key: "tags"
123456789101112

The above logs show which property is traversed in each iteration.

How to flatten an object in JavaScript? #

The code is similar to typescript, we only need to remove the type anotations.

export const flattenObject = (obj, parentKey) => {
  let result = {};

  Object.keys(obj).forEach((key) => {
    const value = obj[key];
    const _key = parentKey ? parentKey + '.' + key : key;
    if (typeof value === 'object') {
      result = { ...result, ...flattenObject(value, _key) };
    } else {
      result[_key] = value;
    }
    console.log(`parentKey: "${parentKey}", _key: "${_key}"`);
  });

  return result;
};
12345678910111213141516

Other Versions #

If you're using ES2017, you can use Object.entries to further simplify the code:

export const flattenObject = (obj: Object, parentKey?: string) => {
  let result = {};

  Object.entries(obj).forEach(([key, value]) => {
    const _key = parentKey ? parentKey + '.' + key : key;
    if (typeof value === 'object') {
      result = { ...result, ...flattenObject(value, _key) };
    } else {
      result[_key] = value;
    }
  });

  return result;
};
1234567891011121314

If you have no access to typescript and need to support older browsers, you can use the JavaScript for..in operator to traverse and assign the properties to the result object:

export function flattenObject(obj: Object, parentKey?: string) {
  let result: Object = {};
  for (let key in obj) {
    if (!obj.hasOwnProperty(key)) continue;
    const value = obj[key];
    const _key = parentKey ? parentKey + '.' + key : key;
    if (typeof value == 'object') {
      let childObject = flattenObject(value, _key);

      for (let childKey in childObject) {
        if (!childObject.hasOwnProperty(childKey)) continue;
        result[childKey] = childObject[childKey];
      }
    } else {
      result[_key] = value;
    }
  }
  return result;
}
12345678910111213141516171819

What else can we do? #

Depending what you need, you might not need to flatten the array. You can just add a condition to check if the value is not an array using Array.isArray(obj).

Another thing you can do is, you can use the flattened keys, in combination with lodash.set method to easily create / set properties in another object.

Conclusion #

We learned how to flatten an object in typescript and JavaScript, along with other ES versions that we might want to consider using.

We also learned the benefits and the uses when flattening an array in JavaScript and Typescript. Furthermore, we learned a little bit about recursive functions. Recursive functions are very powerful in any programming language, make sure you know it well!

If you like this tutorial, please leave a like or share this article. For future tutorials like this, please subscribe to our newsletter or follow me on Twitter.

Related Posts #

Further improve your JavaScript knowledge by reading these posts!

Credits: Image by 畅 苏 from Pixabay

Share this post!

Related Posts

Disclaimer

This content may contain links to products, software and services. Please assume all such links are affiliate links which may result in my earning commissions and fees.
As an Amazon Associate, I earn from qualifying purchases. This means that whenever you buy a product on Amazon from a link on our site, we receive a small percentage of its price at no extra cost to you. This helps us continue to provide valuable content and reviews to you. Thank you for your support!
Donate to ReactHustle