using lodash .groupBy. how to add your own keys for grouped output?
Asked Answered
B

12

154

I have this sample data returned from an API.

I'm using Lodash's _.groupBy to convert the data into an object I can use better. The raw data returned is this:

[
    {
        "name": "jim",
        "color": "blue",
        "age": "22"
    },
    {
        "name": "Sam",
        "color": "blue",
        "age": "33"
    },
    {
        "name": "eddie",
        "color": "green",
        "age": "77"
    }
]

I want the _.groupBy function to return an object that looks like this:

[
    {
        color: "blue",
        users: [
            {
                "name": "jim",
                "color": "blue",
                "age": "22"
            },
            {
                "name": "Sam",
                "color": "blue",
                "age": "33"
            }
        ]
    },
    {
        color: "green",
        users: [
            {
                "name": "eddie",
                "color": "green",
                "age": "77"
            }
        ]
    }
]

Currently I'm using

_.groupBy(a, function(b) { return b.color})

which is returning this.

{blue: [{..}], green: [{...}]}

the groupings are correct, but I'd really like to add the keys I want (color, users). is this possible using _.groupBy? or some other LoDash utility?

Berkeleian answered 12/5, 2014 at 4:6 Comment(1)
As of 2024, there is no need to use lodash, underscore.js or alikey anymore, since native groupBy() is now available.Ickes
C
229

You can do it like this in both Underscore and Lodash (3.x and 4.x).

var data = [{
  "name": "jim",
  "color": "blue",
  "age": "22"
}, {
  "name": "Sam",
  "color": "blue",
  "age": "33"
}, {
  "name": "eddie",
  "color": "green",
  "age": "77"
}];

console.log(
  _.chain(data)
    // Group the elements of Array based on `color` property
    .groupBy("color")
    // `key` is group's name (color), `value` is the array of objects
    .map((value, key) => ({ color: key, users: value }))
    .value()
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>

Original Answer

var result = _.chain(data)
    .groupBy("color")
    .pairs()
    .map(function(currentItem) {
        return _.object(_.zip(["color", "users"], currentItem));
    })
    .value();
console.log(result);

Online Demo

Note: Lodash 4.0 onwards, the .pairs function has been renamed to _.toPairs()

Cryptocrystalline answered 12/5, 2014 at 4:18 Comment(9)
Very nifty, but hard to wrap my head around. Can you explain the steps in between, especially the pairing and zipping (and the double zip, since _.object is an alias for _.zipObject).Affricate
Print the result after each step. It might help you understand better. If you have specific question please let me know. I'll help you with thatCryptocrystalline
lodash 3.10.0 and some logging for every step: jsfiddle.net/plantface/WYCF8/171. It's still a puzzle, but I'm getting there. Haven't used _.zip and _.pair so much yet.Affricate
In lodash 4.x, the pairs() method does not exist anymore. An updated lodash 4.0 version of this example is: .chain(data).groupBy('color') .toPairs().map(function (pair) { return _.zipObject(['Name', 'Suppliers'], air); }).value();Entreaty
I feel like loadash should have a function that does this very exact thing. I would think users would want to group an array of objects by a property all the time.Morehouse
I am getting Object instead of values [ { color: '0', users: [ [Object], [Object] ] } May be a newbie issue - help is greatly appreciated.Poche
The updated version to this answer which worked well is: var result = lodash.chain(res) .groupBy('color') .toPairs() .map(function(currentItem) { return lodash.zipObject(['color', 'users'], currentItem) }) .value();Shipe
Using _.chain is considered a bad practice now.Deering
@ErikSchierboom what is air? You mean pair !Mangan
P
126

Isn't it this simple?

var result = _(data)
            .groupBy(x => x.color)
            .map((value, key) => ({color: key, users: value}))
            .value();
Potaufeu answered 27/10, 2016 at 15:40 Comment(2)
This seems much cleaner to me, and appears to be returning the same results. Thanks!According
wow. what syntax is this? First time seeing that you can pass the array into the constructorInfusive
D
31

Highest voted answer uses Lodash _.chain function which is considered a bad practice now "Why using _.chain is a mistake."

Here is a fewliner that approaches the problem from functional programming perspective:

import tap from "lodash/fp/tap";
import flow from "lodash/fp/flow";
import groupBy from "lodash/fp/groupBy";

const map = require('lodash/fp/map').convert({ 'cap': false });

const result = flow(
      groupBy('color'),
      map((users, color) => ({color, users})),
      tap(console.log)
    )(input)

Where input is an array that you want to convert.

Deering answered 5/1, 2018 at 13:12 Comment(3)
I tried to use flow() instead of chain() here, but typescript's type checking simply go crazy with composed functions. I hope we have the same level of support we currently have in RxJS's pipe(), for example, in lodash.Scald
I really like your map() syntax, very cool map((users, color) => ({color, users})) instead of map((value, key) => ({ color: key, users: value }))Mac
The problem of preventing treeshaking only applies to Lodash. In Underscore, you can customize which functions get mixed into _ since version 1.11.Chirlin
A
26

another way

_.chain(data)
    .groupBy('color')
    .map((users, color) => ({ users, color }))
    .value();
Ambrosane answered 7/12, 2016 at 14:54 Comment(1)
Similar to @thefourtheye's answer but a bit easier to understandUnstable
C
8

Thanks @thefourtheye, your code greatly helped. I created a generic function from your solution using the version 4.5.0 of Lodash.

function groupBy(dataToGroupOn, fieldNameToGroupOn, fieldNameForGroupName, fieldNameForChildren) {
            var result = _.chain(dataToGroupOn)
             .groupBy(fieldNameToGroupOn)
             .toPairs()
             .map(function (currentItem) {
                 return _.zipObject([fieldNameForGroupName, fieldNameForChildren], currentItem);
             })
             .value();
            return result;
        }

To use it:

var result = groupBy(data, 'color', 'colorId', 'users');

Here is the updated fiddler;

https://jsfiddle.net/sc2L9dby/

Correction answered 19/2, 2016 at 18:45 Comment(0)
M
6

Other answers look good by using lodash.

And I would suggest a different approach by using JavaScript, such as Array#reduce, object for storing and ??= syntax. As a result, it just takes O(n) time complexity.

const data = [{ "name": "jim", "color": "blue", "age": "22" }, { "name": "Sam", "color": "blue", "age": "33" }, { "name": "eddie", "color": "green", "age": "77" }];

const result = data.reduce((acc, {
  name,
  color,
  age
}) => {
  acc[color] ??= {
    color: color,
    users: []
  };
  acc[color].users.push({
    name,
    color,
    age
  });

  return acc;
}, {});
console.log(Object.values(result));
Matazzoni answered 5/4, 2021 at 6:26 Comment(0)
P
5

Here is an updated version using lodash 4 and ES6

const result = _.chain(data)
    .groupBy("color")
    .toPairs()
    .map(pair => _.zipObject(['color', 'users'], pair))
    .value();
Platinize answered 9/2, 2017 at 20:28 Comment(0)
M
3

Example groupBy and sum of a column using Lodash 4.17.4

   var data = [{
                "name": "jim",
                "color": "blue",
                "amount": 22
                }, {
                "name": "Sam",
                "color": "blue",
                "amount": 33
                }, {
               "name": "eddie",
               "color": "green",
               "amount": 77
              }];

      var result = _(data)
                   .groupBy(x => x.color)
                   .map((value, key) => 
                   ({color: key,
                    totalamount: _.sumBy(value,'amount'),
                    users: value})).value();

                    console.log(result);
Mihe answered 22/2, 2017 at 22:39 Comment(0)
I
3

ES2024

You can now use the built-in Object.groupBy() or Map.groupBy() in conjunction with the good old map(). No need for lodash, underscore.js or alike anymore:

const input = [
  {
    name: "jim",
    color: "blue",
    age: "22",
  },
  {
    name: "Sam",
    color: "blue",
    age: "33",
  },
  {
    name: "eddie",
    color: "green",
    age: "77",
  },
];

const usersByColor = Object.groupBy(input, (user) => user.color);
const result = Object.entries(usersByColor).map(([color, users]) => ({
  color,
  users,
}));
console.log(JSON.stringify(result, null, 4));
/* Stackoverflow: show only console */
.as-console-wrapper {
  max-height: 100% !important;
  top: 0;
}

Browser support is ok (~ 82% as of 2024-02-15) but not great yet.

Ickes answered 14/2, 2024 at 23:27 Comment(1)
Awesome, did not know this one!Carolyncarolyne
K
2

I would suggest a different approach, using my own library you could do this in a few lines:

var groupMe = sequence(
  groupBy(pluck('color')),
  forOwn(function(acc, k, v) {
    acc.push({colors: k, users: v});
    return acc;
  },[])
);

var result = groupMe(collection);

This would a be a bit difficult with lodash or Underscore because the arguments are in the opposite order order, so you'd have to use _.partial a lot.

Keg answered 12/5, 2014 at 4:34 Comment(0)
Z
0

  const groupBy = (array, key, name) => {
    return array.reduce((result, obj) => {
      result[obj[key]] = result[obj[key]] || {
        location: obj[name],
        child: [],
      };
      // result[obj[key]] ??= {
      //   location: obj[name],
      //   child: []
      // };
      result[obj[key]].child.push(obj);
      return result;
    }, {});
  };

  const items = [
    {
      id: 1,
      age: 75,
      name: 'Kumar',
      location: 'chennai',
    },
    {
      id: 1,
      age: 25,
      name: 'Christo',
      location: 'Dindigul',
    },
    {
      id: 1,
      age: 28,
      name: 'SK',
      location: 'chennai',
    },
    {
      id: 1,
      age: 21,
      name: 'Ram',
      location: 'chennai',
    },
    {
      id: 1,
      age: 21,
      name: 'ravi',
      location: 'chennai',
    },
  ];

  let obj = groupBy(items, 'age', 'location');
  const result = Object.keys(obj).map((key) => [obj[key]][0]);
  console.log(result);

 
Zedekiah answered 12/7, 2022 at 18:18 Comment(1)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Malines
C
-2

In 2017 do so

_.chain(data)
  .groupBy("color")
  .toPairs()
  .map(item => _.zipObject(["color", "users"], item))
  .value();
Cardoon answered 2/6, 2017 at 10:52 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.