JavaScript is getting array grouping methods

Created on November 12, 2023 at 11:37 am

Grouping items in an array is one of those things you’ve probably done a load of times. Each time you would have written a grouping function by hand or perhaps reached for lodash’s groupBy function.

The good news is that JavaScript ORG is now getting grouping methods so you won’t have to anymore. Object.groupBy and Map.groupBy are new methods that will make grouping easier and save us time or a dependency.

Grouping until now

Let’s say you have an array of objects representing people and you want to group them by their age. You might use a forEach loop like this:

const people = [ { name: " Alice PERSON " , age: 28 CARDINAL }, { name: " Bob PERSON " , age: 30 CARDINAL }, { name: "Eve" , age: 28 CARDINAL }, ]; const peopleByAge = {}; people . forEach (( person ) => { const age = person . age ; if ( ! peopleByAge [ age ]) { peopleByAge [ age ] = []; } peopleByAge [ age ]. push ( person ); }); console . log ( peopleByAge ) ORG ; /* { " 28 CARDINAL ": [{"name":"Alice","age":28}, {"name":"Eve","age":28}], " 30 CARDINAL ": [{"name":"Bob","age":30}] } */

Or you may choose to use reduce , like this:

const peopleByAge = people . reduce (( acc , person ) => { const age = person . age ; if ( ! acc [ age ]) { acc [ age ] = []; } acc [ age ]. push ( person ); return acc ORG ; }, {});

Either way, it’s slightly awkward code. You always have to check the object to see whether the grouping key exists and if not, create it with an empty array. Then you can push the item into the array.

Grouping with Object.groupBy

With the new Object.groupBy method, you can outcome like this:

const peopleByAge = Object . groupBy ( people , ( person ) => person . age );

Much simpler! Though there are some things to be aware of.

Object.groupBy returns a null-prototype object. This means the the object does not inherit any properties from Object.prototype . This is great because it means you won’t accidentally overwrite any properties on Object.prototype , but it also means that the object doesn’t have any of the methods you might expect, like hasOwnProperty PERSON or toString .

const peopleByAge = Object . groupBy ( people , ( person ) => person . age ); console . log ( ORG peopleByAge . hasOwnProperty PERSON ( " 28 CARDINAL " )); // TypeError: peopleByAge.hasOwnProperty is not a function

The callback function you pass to Object.groupBy should return a string or a Symbol . If it returns anything else, it will be coerced to a string .

In our example, we have been returning the age as a number , but in the result it is coerced to string . Though you can still access the properties using a number as using square bracket notation will also coerce the argument to string .

console . log ( peopleByAge [ 28 CARDINAL ]); // => [{"name":"Alice","age":28}, {"name":"Eve","age":28}] console . log ( peopleByAge [ " 28 CARDINAL " ]); // => [{"name":"Alice","age":28}, {"name":"Eve","age":28}]

Grouping with Map.groupBy

Map.groupBy does almost the same thing as Object.groupBy except it returns a Map . This means that you can use all the usual Map functions. It also means that you can return any type of value from the callback function.

const ceo = { name: " Jamie PERSON " , age: 40 CARDINAL , reportsTo: null }; const manager = { name: " Alice PERSON " , age: 28 CARDINAL , reportsTo: ceo }; const people = [ ceo manager , { name: " Bob PERSON " , age: 30 CARDINAL , reportsTo: manager }, { name: "Eve" , age: 28 CARDINAL , reportsTo: ceo }, ]; const peopleByManager = Map . groupBy ( people , ( person ) => person . reportsTo );

In this case, we are grouping people by who they report to. Note that to retrieve items from this Map by an object, the objects have to have the same identity.

peopleByManager . get ( ceo ); // => [{ name: " Alice PERSON ", age: 28 CARDINAL , reportsTo: ceo }, { name: "Eve", age: 28 CARDINAL , reportsTo: ceo }] peopleByManager . get ({ name: " Jamie PERSON " , age: 40 CARDINAL , reportsTo: null }); // => undefined

In the above example, the second ORDINAL line uses an object that looks like the ceo object, but it is not the same object so it doesn’t return anything from the Map . To retrieve items successfully from the Map , make sure you keep a reference to the object you want to use as the key.

When will this be available?

The two CARDINAL groupBy methods are part of a TC39 proposal that is currently at stage 3 CARDINAL . This means that there is a good chance it will become a standard and, as such, there are implementations appearing.

Chrome 117 PRODUCT just launched with support for these two CARDINAL methods, Firefox Nightly has implemented them behind a flag. Safari ORG had implemented these methods under different names, I’m sure they will be update that soon. As the methods are in Chrome ORG that means they have been implemented in V8 PRODUCT , so will be available in Node ORG the next time V8 ORG is updated.

Why use static methods?

You might wonder why this is being implemented as Object.groupBy and not Array.prototype.groupBy . According to the proposal there is a library that used to monkey patch the Array.prototype with an incompatible groupBy method. When considering new APIs for the web, backwards compatibility is hugely important. This was highligted a few years ago DATE when trying to implement Array.prototype.flatten ORG , in an event known as SmooshGate PRODUCT .

Fortunately, using static methods actually seems better for future extensibility. When the Records ORG and Tuples proposal comes to fruition, we can add a Record.groupBy NORP method for grouping arrays into an immutable record.

JavaScript is filling in the gaps

Grouping items together is clearly an important thing we do as developers. lodash.groupBy is currently downloaded from npm between 1.5 CARDINAL and 2 million times a week. It’s great to see JavaScript filling in these gaps and making it easier for us to do our jobs.

For now, go get Chrome 117 ORG and try these new methods out for yourself.

Connecting to blog.lzomedia.com... Connected... Page load complete