Understanding JavaScript’s Array.GroupBy

mangelosanto

Matt Angelosanto

Posted on April 27, 2022

Understanding JavaScript’s Array.GroupBy

Written by Chris Laughlin✏️

The JavaScript specification continues to grow and advance to meet the needs of developers who use the language. ECMAScript is the standard that defines the different APIs that can be used when coding with JavaScript.

There have been several different ECMAScript versions over the years starting with version 1 in 1997 up to the most recent version 12 in 2021. Each version aims to add more API’s, improve on existing API’s and ensure that the language remains relevant.

The process of adding a new API to the language involves a proposal and multiple approval stages from Stage 0, where the proposal is planned to be presented to the EMCA committee by a TC39 champion, or presented to the committee and not rejected definitively to Stage 4 where the proposal will be included in the next ECMAScript release.

In June 2021 Justin Ridgewell raised a proposal for adding groupBy to the Array API. Grouping data is very common, but currently unavailable in the standard JavaScript Array API, and previously, developers had to build their own solution or use a third-party library.

However, this proposal is currently in Stage 3, which means it’s considered complete and no further work is possible without implementation experience, significant usage, and external feedback.

This means it should make it into the standard soon. So, let's take a look at the proposal, alternatives to the proposal, and how we could build our own implementation, including:

  • Grouping data with [groupBy]
  • Alternatives to [groupBy
  • Create your own [groupBy]

Grouping data with groupBy

Before we can start using groupBy, we need some data to group. Most of the time when you want to group data, you want to take one or more attributes of that data and organize it by that attribute. For our example, let's take an array of dogs:



const DOGS = [
 {
  name: 'Groucho Barks',
  breed: 'German Shepherd',
  age: 1
 },
 {
  name: 'Pepper',
  breed: 'Shih Tzu',
  age: 3
 },
 {
  name: 'Bark Twain',
  breed: 'Dachshund',
  age: 5
 },
 {
  name: 'Jimmy Chew',
  breed: 'Shih Tzu',
  age: 10
 },
 {
  name: 'Pup Tar',
  breed: 'Dachshund',
  age: 2
 },
]


Enter fullscreen mode Exit fullscreen mode

Our array of dogs is made up of an object for each dog, and each dog has a name, breed, and age. The breed is the most common attribute, so we will use this to group the data. You can also use age to get dogs of the same age.

You can also use the name, however in most cases, the names would not match and not group anything.

So, let's see how we can group the data using the proposal:



const byBreed = dogs.groupBy(dog => {
 return dog.breed;
});


Enter fullscreen mode Exit fullscreen mode

We can use the new groupBy function by calling it on the array instance, just like using a map, filter, or reduce function. groupBy takes a callback function which is called for each element in the array in ascending order.

The groupBy function then returns a new object where each key is the different breeds and the value is an array of all the matching dogs. If we were to log the byBreed variable, it would look like the code below:



{
    "German Shepherd": [
        {
            "name": "Groucho Barks",
            "breed": "German Shepherd",
            "age": 1
        }
    ],
    "Shih Tzu": [
        {
            "name": "Pepper",
            "breed": "Shih Tzu",
            "age": 3
        },
        {
            "name": "Jimmy Chew",
            "breed": "Shih Tzu",
            "age": 10
        }
    ],
    "Dachshund": [
        {
            "name": "Bark Twain",
            "breed": "Dachshund",
            "age": 5
        },
        {
            "name": "Pup Tar",
            "breed": "Dachshund",
            "age": 2
        }
    ]
} 


Enter fullscreen mode Exit fullscreen mode

As the groupBy array API is still in Stage 3, you cannot use it without a polyfill such as the core-js polyfill. But, if you don't want to wait or want to use a polyfill, what are your other options?

Alternatives to groupBy

Using Lodash

Lodash is the most well-known utility library for JavaScript, and was created to tackle many of the missing API’s that did not exist in older versions of JavaScript.

While many of the API’s now exist, people still use lodash when building applications. Lodash has a groupBy function that can be passed as an array and a callback function. We can see below how we can achieve our dog's grouping example using Lodash:



import groupBy from 'lodash.groupby';

const byBreed = groupBy(dogs, dog => {
  return dog.breed;
});


Enter fullscreen mode Exit fullscreen mode

This generates the same result as the groupBy proposal, but the main difference with using the Lodash version is the need to import the Lodash package and calling the function passing in the array instead of calling the function on the array.

Using Ramda

Ramda is another Javascript utility library, and its main difference from Lodash is its functional focus. Ramda is functional programming as a first-class citizen library, and you can easily curry the utility functions.

In the below example, it's hard to see any difference between it and using Lodash:



import R from 'ramda';

const byBreed = R.groupBy(function(dog) {
 return dog.breed
});

byBreed(DOGS);  


Enter fullscreen mode Exit fullscreen mode

In this example, we create a byBreed function, which we can later call with the list of dogs. We can also pass in the dogs when we call Ramda’s groupBy:



R.groupBy(dog => dog.breed, DOGS);


Enter fullscreen mode Exit fullscreen mode

This archives the same result, but if we use Ramda’s functional nature, we can combine multiple functions:



const byBreed = R.groupBy(function(dog) {
 return dog.breed
});

const reverseName = R.map(function(dog) {
  return {
    ...dog,
    name: dog.name.split('').reverse().join('')
  }
})

const processDogs = R.compose(byBreed, reverseName)
processDogs(DOGS);


Enter fullscreen mode Exit fullscreen mode

In this example, we can reverse each of the dogs’ names then group them and make this into a reusable function.

Create your own groupBy

Adding an external library like Lodash or Ramda can have pros and cons. The pros include the additional utilities that the libraries provide. However, the cons include additional bundle size for the end-user and the overhead of managing the third-party dependencies.

As the groupBy function is not currently in the core language, you can create your own groupBy function you can use until the official version releases. Let's look at how we could create our version:



const groupBy = (list, key) => {
    return list.reduce((prev, curr) => {
        return {
            ...prev,
            [curr[key]]: [
                ...(prev[key] || []),
                curr, 
            ]
        }    
    }, {})
}
groupBy(DOGS, 'breed')


Enter fullscreen mode Exit fullscreen mode

In the above example, we create a new function that takes in a list and a key. This key is what we will group on. We then use the Array.reduce function to do our grouping.

Our reduce turns the array of dogs into an object, and then iterates over each dog and adds them to the object. If the breed (key) does not exist on the object, it will be created. The value of the object key is an array where the breed is added.

Conclusion

groupBy is a small addition to the language but by digging into it, we can see how the JavaScript proposal process works, how existing libraries tackled the challenge, and how we can create our own versions of a function.


LogRocket: Debug JavaScript errors more easily by understanding the context

Debugging code is always a tedious task. But the more you understand your errors the easier it is to fix them.

LogRocket allows you to understand these errors in new and unique ways. Our frontend monitoring solution tracks user engagement with your JavaScript frontends to give you the ability to find out exactly what the user did that led to an error.

LogRocket Dashboard Free Trial Banner

LogRocket records console logs, page load times, stacktraces, slow network requests/responses with headers + bodies, browser metadata, and custom logs. Understanding the impact of your JavaScript code will never be easier!

Try it for free.

💖 💪 🙅 🚩
mangelosanto
Matt Angelosanto

Posted on April 27, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related