147

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?

Lior Cohen
  • 8,985
  • 2
  • 29
  • 27
user1767105
  • 1,919
  • 3
  • 15
  • 10

11 Answers11

224

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()

Julian
  • 4,176
  • 19
  • 40
thefourtheye
  • 233,700
  • 52
  • 457
  • 497
  • 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`). – Benny Bottema Jul 31 '15 at 09:17
  • 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 that – thefourtheye Jul 31 '15 at 09:19
  • 3
    lodash 3.10.0 and some logging for every step: http://jsfiddle.net/plantface/WYCF8/171/. It's still a puzzle, but I'm getting there. Haven't used `_.zip` and `_.pair` so much yet. – Benny Bottema Jul 31 '15 at 09:24
  • 12
    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();` – Erik Schierboom Jan 20 '16 at 14:17
  • 7
    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. – Adam Klein Feb 06 '16 at 17:46
  • I am getting Object instead of values [ { color: '0', users: [ [Object], [Object] ] } May be a newbie issue - help is greatly appreciated. – Pikachu-go Nov 08 '16 at 05:25
  • 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();` – Ben Cochrane Nov 04 '17 at 02:43
  • 1
    Using `_.chain` is considered a bad practice now. – Pawel Jan 05 '18 at 13:18
  • @ErikSchierboom what is air? You mean `pair` ! – Mo. Jan 07 '22 at 11:48
123

Isn't it this simple?

var result = _(data)
            .groupBy(x => x.color)
            .map((value, key) => ({color: key, users: value}))
            .value();
Darragh
  • 2,526
  • 1
  • 23
  • 31
30

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.

Pawel
  • 3,558
  • 1
  • 25
  • 24
  • 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. – Bruno Medeiros Nov 12 '19 at 07:56
  • I really like your map() syntax, very cool `map((users, color) => ({color, users}))` instead of `map((value, key) => ({ color: key, users: value }))` – Gil Epshtain May 12 '20 at 13:54
  • 1
    The problem of preventing treeshaking only applies to Lodash. In Underscore, you can customize which functions get mixed into `_` since version 1.11. – Julian Apr 06 '21 at 16:53
26

another way

_.chain(data)
    .groupBy('color')
    .map((users, color) => ({ users, color }))
    .value();
stasovlas
  • 7,136
  • 2
  • 28
  • 29
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/

Andrew
  • 7,848
  • 1
  • 26
  • 24
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));
Nguyễn Văn Phong
  • 13,506
  • 17
  • 39
  • 56
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();
Matt
  • 1,388
  • 15
  • 16
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);
Alexei - check Codidact
  • 22,016
  • 16
  • 145
  • 164
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.

elclanrs
  • 92,861
  • 21
  • 134
  • 171
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);

 
  • 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](/help/how-to-answer). – Community Jul 13 '22 at 12:28
-2

In 2017 do so

_.chain(data)
  .groupBy("color")
  .toPairs()
  .map(item => _.zipObject(["color", "users"], item))
  .value();