DEV Community

Cover image for Simplify, Groupify, Conquer: The convenience of Object.GroupBy() in JavaScript
Subaash
Subaash

Posted on • Edited on

Simplify, Groupify, Conquer: The convenience of Object.GroupBy() in JavaScript

It was a regular day of coding when I decided to revisit an old project of mine. As I scrolled through my code, I noticed a piece of code I had written during the earlier days of my JavaScript journey. The project was an Expense Tracker web application that allowed users to manually enter transactions and store them in LocalStorage. The specific code snippet I encountered was related to the grouping of data.

Back then, I remember grappling with the challenges of grouping data and attempting solutions using filter(), map(), and even forEach(). However, to my surprise, I discovered that there was an actual method designed for conveniently grouping data.

A sample from the actual set of data to be handled is as follows:



const transacHistory = [
    { type: 'Income', desc: 'Salary', amount: '52000', date: '09/01/2023' },
    { type: 'Expense', desc: 'House rent', amount: '7500', date: '09/03/2023'},
    { type: 'Expense', desc: 'Shopping', amount: '8000', date: '09/11/2023' },
    { type: 'Income', desc: 'YouTube', amount: '3508', date: '09/12/2023' },
    { type: 'Expense', desc: 'Veggies', amount: '550', date: '9/02/2023' },
    { type: 'Expense', desc: 'Internet', amount: '2250', date: '09/11/2023' },
    { type: 'Expense', desc: 'Veggies', amount: '350', date: '9/18/2023' },
]


Enter fullscreen mode Exit fullscreen mode

Now, I wanted to group them based on the type of transaction made ( expense or income )

The old-school data grouping technique



const groupedTransacHistory = transacHistory.reduce((acc, item)=>{
    if(!Object.keys(acc).includes(item.type)){
        acc[item.type] = [item]
    }
    else{
        acc[item.type].push(item)
    }
    return acc;
}, [])
console.log(groupedTransacHistory)

{*
OUTPUT:
[
  Income: [
    { type: 'Income', desc: 'Salary', amount: '52000', date: '09/01/2023' },
    { type: 'Income', desc: 'YouTube', amount: '3508', date: '09/12/2023' }
  ],
  Expense: [
    { type: 'Expense', desc: 'House rent', amount: '7500', date: '09/03/2023' },
    { type: 'Expense', desc: 'Shopping', amount: '8000', date: '09/11/2023' },
    { type: 'Expense', desc: 'Veggies', amount: '550', date: '9/02/2023' },
    { type: 'Expense', desc: 'Internet', amount: '2250', date: '09/11/2023' },
    { type: 'Expense', desc: 'Veggies', amount: '350', date: '9/18/2023' }
  ]
]
*}



Enter fullscreen mode Exit fullscreen mode

The code to achieve the above output might seem to be a bit complex, but anyone familiar with the filter() method might understand the logic. If you wish to know more about JavaScript's filter() method, refer to the official MDN docs embedded at the bottom of this article. The same logic can also be implemented using map() and forEach() methods as well. As their logic is similar, I've included only the code associated with the filter() method.

Know more about the groupBy() method:

Before letting you know the actual solution using the proposed groupBy() method, let's analyse how to use that in the first place.
The Object.groupBy() method is used to categorize individual items of an iterable. The categorization is done according to the coerced string values returned by the provided callback function. The grouped/categorized object thus returned contains unique keys with each key corresponding to an array of related values. Is it sound confusing? You are on the right path. Follow along to see this method in action.

SYNTAX

Syntax of Object.groupBy() method in JavaScript
where,

  • The iterableis the element to be grouped

  • The callbackFunctionis the function to be passed to execute each element in the iterable. This function should return a value which is then coerced into a string.
    function callbackFunction(element, index){...}

  • The callbackFunctionaccepts elementand indexas its arguments
    where,

  • The element represents the current element which is being passed to the callbackFunction at the moment and the index is the index of the element itself.
    Let's look at some general and simple examples to grasp the concept.

Examples

The following code groups items based on whether they start with a vowel or a consonant.

1. The traditional approach:



const vowels = ['a', 'e', 'i', 'o', 'u']
const cartItems = ['apples', 'bananas', 'cherries', 'umbrellas', 'cat food', 'pedigree', 'cupcakes']
const res2 = cartItems.reduce((accumulator, item) => {
    if (vowels.includes(item.slice(0, 1))) {
        if (!accumulator['vowels']) {
            accumulator['vowels'] = [];
        }
        accumulator['vowels'].push(item);
    } else {
        if (!accumulator['non-vowels']) {
            accumulator['non-vowels'] = [];
        }
        accumulator['non-vowels'].push(item);
    }
    return accumulator;
}, {});
{*
OUTPUT: 
{
  vowels: ['apples', 'umbrellas'],
  non-vowels: ['bananas', 'cherries', 'cat food', 'pedigree', 'cupcakes']
}
*}


Enter fullscreen mode Exit fullscreen mode

2. The Object.groupBy() approach:



const vowels = ['a', 'e', 'i', 'o', 'u']
const cartItems = ['apples', 'bananas', 'cherries', 'umbrellas', 'cat food', 'pedigree', 'cupcakes']
Object.groupBy(cartItems, (item)=>vowels.includes(item.slice(0, 1)))
//item.slice(0,1) returns the first letter of the currently iterated item and checks whether it is a vowel.
{*
OUTPUT: 
{
  true: ['apples', 'umbrellas'],
  false: ['bananas', 'cherries', 'cat food', 'pedigree', 'cupcakes']
}
*}


Enter fullscreen mode Exit fullscreen mode

If there are no object properties to be displayed in the case of arrays, the Object.groupBy() method simply uses the Boolean values as keys. If we need precisely set keys, then we should implement Map.groupBy() method which is the replica of our former method but lets us set keys instead of the default Boolean.



const fictionalCharacters = [
  { character: 'Ironman', ability: 'Fights bad guys' },
  { character: 'Barbie', ability: "Doesn't fight" },
  { character: 'Black widow', ability: 'Fights bad guys' },
  { character: 'Oggy', ability: "Doesn't fight" },
  { character: 'Peppa Pig', ability: "Doesn't fight" }
]
Object.groupBy( fictionalCharacters, (item)=>item.ability )
{* The value of item.ability is type-converted into a string and used as a key to group similar items in arrays, where the key's value is an array of corresponding items. Example: { item.ability: [{}, {}] } *}
{*
OUTPUT:
[
   Fights bad guys: [
      { character: 'Ironman', ability: 'Fights bad guys' },
      { character: 'Black widow', ability: 'Fights bad guys' },
   ],
   Doesn't fight: [
      { character: 'Barbie', ability: "Doesn't fight" },
      { character: 'Oggy', ability: "Doesn't fight" },
      { character: 'Peppa Pig', ability: "Doesn't fight" }
   ]
]
*}


Enter fullscreen mode Exit fullscreen mode

So, as now we have a pretty decent understanding of how it works, let's get back to our expense tracker example.



const transacHistory = [
    { type: 'Income', desc: 'Salary', amount: '52000', date: '09/01/2023' },
    { type: 'Expense', desc: 'House rent', amount: '7500', date: '09/03/2023'},
    { type: 'Expense', desc: 'Shopping', amount: '8000', date: '09/11/2023' },
    { type: 'Income', desc: 'YouTube', amount: '3508', date: '09/12/2023' },
    { type: 'Expense', desc: 'Veggies', amount: '550', date: '9/02/2023' },
    { type: 'Expense', desc: 'Internet', amount: '2250', date: '09/11/2023' },
    { type: 'Expense', desc: 'Veggies', amount: '350', date: '9/18/2023' },
]
Object.groupBy(transacHistory, (item)=>item.type)
{*
OUTPUT:
[
  Income: [
    { type: 'Income', desc: 'Salary', amount: '52000', date: '09/01/2023' },
    { type: 'Income', desc: 'YouTube', amount: '3508', date: '09/12/2023' }
  ],
  Expense: [
    { type: 'Expense', desc: 'House rent', amount: '7500', date: '09/03/2023' },
    { type: 'Expense', desc: 'Shopping', amount: '8000', date: '09/11/2023' },
    { type: 'Expense', desc: 'Veggies', amount: '550', date: '9/02/2023' },
    { type: 'Expense', desc: 'Internet', amount: '2250', date: '09/11/2023' },
    { type: 'Expense', desc: 'Veggies', amount: '350', date: '9/18/2023' }
  ]
]
*}


Enter fullscreen mode Exit fullscreen mode

Simple as that. This is how the groupBy() method is convenient to use and understand.

NOTE:

  • Instead of making a huge leap to the static methods like Object.groupBy(), beginners should be comfortable using map(), filter() and reduce() methods.

  • The Object.groupBy() method, as of 17th Dec 2023, is not supported in all browsers and it is expected to be in the near future.

Browser compatibility of Object.groupBy() method

Refer to the following document to learn more about the filter() method in JavaScript:

Array.prototype.filter() - JavaScript | MDN

The filter() method of Array instances creates a shallow copy of a portion of a given array, filtered down to just the elements from the given array that pass the test implemented by the provided function.

favicon developer.mozilla.org



To know more about Object.groupBy() method, refer to the following official docs by MDN

Object.groupBy() - JavaScript | MDN

The Object.groupBy() static method groups the elements of a given iterable according to the string values returned by a provided callback function. The returned object has separate properties for each group, containing arrays with the elements in the group.

favicon developer.mozilla.org

Follow me along my web development journey on twitter/X

Top comments (9)

Collapse
 
vikranthkannan profile image
Vikranth

Good article. BTW, how does it differ from the other methods like filter(), map() and reduce() in performance?

Collapse
 
subaash_b profile image
Subaash

Hi, thanks for the feedback. It does perform well when compared to filter(), map() and reduce().
The vowel example stated above consumes 6 times more time than to execute the same with the Object.groupBy() method. Check them out using console.time() and console.timeEnd().

Collapse
 
vikranthkannan profile image
Vikranth

Wait. What? Is there an actual console.time() in JavaScript? Don't mistake me, I'm a beginner though.

Thread Thread
 
subaash_b profile image
Subaash

I never thought you would wonder on seeing console.time(). There are actually many console methods available within JavaScript like

  • console.error()
  • console.warn()
  • console.table()
  • console.dir()
  • console.group()
  • console.trace()
  • console.time() / console.timeEnd()
  • console.clear() You will come across them along your coding journey.
Thread Thread
 
vikranthkannan profile image
Vikranth

Oh my god. There is actually lot to learn 😶‍🌫️. Hope I would complete them before I complete my UG degree.

Thread Thread
 
subaash_b profile image
Subaash

Remember, the most successful developers are those who are persistent, curious, and enjoy the process of continuous learning. Celebrate your progress along the way, and don't hesitate to seek help or collaborate with others when needed. Good luck on your learning journey!

Collapse
 
ekdikeo profile image
Eric B

How to use a function that doesn't exist and hasn't been implemented. Brilliant.

Collapse
 
subaash_b profile image
Subaash

Your keen observation skills truly deserve a golden star. Despite the desclaimer on the Note section, the context about the compatibility issue seems to be playing hide-and-seek with some of the readers. Btw, thanks for pointing out the concern and I hope my next work will be smoother. Have a happy holiday !!

Collapse
 
neel9484 profile image
Info Comment hidden by post author - thread only accessible via permalink
Neel

Y7mate Downloader: you can use websites like This y7mate to download videos quickly and easily. You can also download thumbnails and MP3s from there.

Some comments have been hidden by the post's author - find out more