Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Group Array of JavaScript Objects by Key or Property Value

Group array of JavaScript objects by keys

This fork of JamieMason's implementation changes the key parameter to be an array of keys instead of just a single key. This makes it possible to group by multiple properties instead of just one.

Implementation

const groupBy = keys => array =>
  array.reduce((objectsByKeyValue, obj) => {
    const value = keys.map(key => obj[key]).join('-');
    objectsByKeyValue[value] = (objectsByKeyValue[value] || []).concat(obj);
    return objectsByKeyValue;
  }, {});
Click to see TypeScript version
/**
 * Group array of objects by given keys
 * @param keys keys to be grouped by
 * @param array objects to be grouped
 * @returns an object with objects in `array` grouped by `keys`
 * @see <https://gist.github.com/mikaello/06a76bca33e5d79cdd80c162d7774e9c>
 */
const groupBy = <T>(keys: (keyof T)[]) => (array: T[]): Record<string, T[]> =>
  array.reduce((objectsByKeyValue, obj) => {
    const value = keys.map((key) => obj[key]).join('-');
    objectsByKeyValue[value] = (objectsByKeyValue[value] || []).concat(obj);
    return objectsByKeyValue;
  }, {} as Record<string, T[]>);

Usage

const cars = [
  { brand: 'Audi', produced: '2016', color: 'black' },
  { brand: 'Audi', produced: '2017', color: 'white' },
  { brand: 'Ford', produced: '2016', color: 'red' },
  { brand: 'Ford', produced: '2016', color: 'white' },
  { brand: 'Peugot', produced: '2018', color: 'white' }
];

const groupByBrand = groupBy(['brand']);
const groupByColor = groupBy(['color']);
const groupByBrandAndYear = groupBy(['brand', 'produced']);

console.log(
  JSON.stringify({
    carsByBrand: groupByBrand(cars),
    carsByColor: groupByColor(cars),
    carsByBrandAndYear: groupByBrandAndYear(cars)
  }, null, 2)
);

Output

{
  "carsByBrand": {
    "Audi": [
      {
        "brand": "Audi",
        "produced": "2016",
        "color": "black"
      },
      {
        "brand": "Audi",
        "produced": "2017",
        "color": "white"
      }
    ],
    "Ford": [
      {
        "brand": "Ford",
        "produced": "2016",
        "color": "red"
      },
      {
        "brand": "Ford",
        "produced": "2016",
        "color": "white"
      }
    ],
    "Peugot": [
      {
        "brand": "Peugot",
        "produced": "2018",
        "color": "white"
      }
    ]
  },
  "carsByColor": {
    "black": [
      {
        "brand": "Audi",
        "produced": "2016",
        "color": "black"
      }
    ],
    "white": [
      {
        "brand": "Audi",
        "produced": "2017",
        "color": "white"
      },
      {
        "brand": "Ford",
        "produced": "2016",
        "color": "white"
      },
      {
        "brand": "Peugot",
        "produced": "2018",
        "color": "white"
      }
    ],
    "red": [
      {
        "brand": "Ford",
        "produced": "2016",
        "color": "red"
      }
    ]
  },
  "carsByBrandAndYear": {
    "Audi-2016": [
      {
        "brand": "Audi",
        "produced": "2016",
        "color": "black"
      }
    ],
    "Audi-2017": [
      {
        "brand": "Audi",
        "produced": "2017",
        "color": "white"
      }
    ],
    "Ford-2016": [
      {
        "brand": "Ford",
        "produced": "2016",
        "color": "red"
      },
      {
        "brand": "Ford",
        "produced": "2016",
        "color": "white"
      }
    ],
    "Peugot-2018": [
      {
        "brand": "Peugot",
        "produced": "2018",
        "color": "white"
      }
    ]
  }
}

See playcode.io for example.

@MaheshStark

This comment has been minimized.

Copy link

@MaheshStark MaheshStark commented Apr 5, 2020

is there any way to get the count which group have

@mikaello

This comment has been minimized.

Copy link
Owner Author

@mikaello mikaello commented Apr 5, 2020

is there any way to get the count which group have

Yes, that is possible. The values of a group is just an array, so you could just check the length property of that array:

const groupBy = (keys) => (array) =>
  array.reduce((objectsByKeyValue, obj) => {
    const value = keys.map((key) => obj[key]).join("-");
    objectsByKeyValue[value] = (objectsByKeyValue[value] || []).concat(obj);
    return objectsByKeyValue;
  }, {});

const cars = [
  { brand: "Audi", produced: "2016", color: "black" },
  { brand: "Audi", produced: "2017", color: "white" },
  { brand: "Ford", produced: "2016", color: "red" },
  { brand: "Ford", produced: "2016", color: "white" },
  { brand: "Peugot", produced: "2018", color: "white" },
];

const groupByBrandAndYear = groupBy(["brand", "produced"]);

for (let [groupName, values] of Object.entries(groupByBrandAndYear(cars))) {
  console.log(`${groupName}: ${values.length}`);
}
# console output
Audi-2016: 1
Audi-2017: 1
Ford-2016: 2
Peugot-2018: 1
@maheshUni2020

This comment has been minimized.

Copy link

@maheshUni2020 maheshUni2020 commented Apr 5, 2020

Thank it is very useful to me

@phil-epl

This comment has been minimized.

Copy link

@phil-epl phil-epl commented Jun 19, 2020

Is there any way to construct the function without Arrow functions? Confused how to deconstruct into a "normal" function but unable to use arrow functions in my implementation @mikaello

@mikaello

This comment has been minimized.

Copy link
Owner Author

@mikaello mikaello commented Jun 20, 2020

@eplPA: You can convert arrow function to old school function this way:

  • (param) => val: function(param) { return val }
  • (param) => { return val }: function(param) { return val }

Read more about arrow functions on MDN

@phil-epl

This comment has been minimized.

Copy link

@phil-epl phil-epl commented Jun 22, 2020

Thanks for the response @mikaello
I understand the basics of converting arrow functions, I am just having trouble visualizing this one specifically because there are multiple arrows.

@mikaello

This comment has been minimized.

Copy link
Owner Author

@mikaello mikaello commented Jun 22, 2020

I see. It is exactly the same concept as with a single arrow, just more function():

a => b => c
// Is the same as
function(a) { 
  return function(b) { 
    return c
  }
}

This is a technique called currying. There are lots of articles about JavaScript and currying, e.g. Currying in Javascript — arrow function sequence
(which has examples with both function() and arrow-function).

@phil-epl

This comment has been minimized.

Copy link

@phil-epl phil-epl commented Jun 24, 2020

EDIT** I actually think I got it... if anyone needs an non-arrow function version this seems to be working for me:

function groupBy(keys) {
  return function(array) {
    return array.reduce(function(objectsByKeyValue, obj) {
      var value = keys.map(function(key){return (obj[key])}).join('-');
      objectsByKeyValue[value] = (objectsByKeyValue[value] || []).concat(obj);
      return objectsByKeyValue; 
    }, {})
  } 
}
@mikaello

This comment has been minimized.

Copy link
Owner Author

@mikaello mikaello commented Jun 24, 2020

I'm glad you figured it out :-)

@MaheshStark

This comment has been minimized.

Copy link

@MaheshStark MaheshStark commented Jun 25, 2020

Thank you

@MaccerC

This comment has been minimized.

Copy link

@MaccerC MaccerC commented Jul 29, 2020

@mikaello In my code I use groupArray=require('group-array) which allows me to group an array on multiple properties. Your code does the same, but with a slightly different output.
You're concatenate the 2 keys like so: "Audi - 2016". What I want as outcome is: { "Audi": { 2016: [{...}] } }
Is this format also possible with your code?

@mikaello

This comment has been minimized.

Copy link
Owner Author

@mikaello mikaello commented Jul 30, 2020

@MaccerC

Is this format also possible with your code?

With a modification of how the key is created it is possible.

Group by with a nested structure:

const groupBy = (keys) => (array) =>
  array.reduce((objectsByKeyValue, obj) => {
   // Instead of creating a unique key for each grouped by values, we are now traversing (and building) 
   // the whole object structure for every array value:
    keys.reduce((builder, key, index) => {
      if (index !== keys.length - 1) {
        // Building the nested grouped by structure
        builder[obj[key]] = builder[obj[key]] || {};
      } else {
        // Appending the current object at the leaf node
        builder[obj[key]] = (builder[obj[key]] || []).concat(obj);
      }
      return builder[obj[key]];
    }, objectsByKeyValue);

    return objectsByKeyValue;
  }, {});

Example:

console.log(
  JSON.stringify(
    groupBy(["a", "b", "c"])([
      { a: "xxx", b: "yyy", c: "zzz", q: "111" },
      { a: "xxx", b: "yyy", c: "diff" },
      { a: "xxx", b: "yyy", c: "zzz", t: "222" },
    ]),
    null,
    2,
  ),
);

/*
{
  "xxx": {
    "yyy": {
      "zzz": [
        {
          "a": "xxx",
          "b": "yyy",
          "c": "zzz",
          "q": "111"
        },
        {
          "a": "xxx",
          "b": "yyy",
          "c": "zzz",
          "t": "222"
        }
      ],
      "diff": [
        {
          "a": "xxx",
          "b": "yyy",
          "c": "diff"
        }
      ]
    }
  }
}
*/
@MaccerC

This comment has been minimized.

Copy link

@MaccerC MaccerC commented Aug 5, 2020

@mikaello Very good! This code works like a charm. Thank you!

@DYW972

This comment has been minimized.

Copy link

@DYW972 DYW972 commented Sep 3, 2020

Very efficient ! Just a question it’s possible to change the key name ? Exemples mix it with a String, convert it into number (for exemple if the value is a time stamp).

@mikaello

This comment has been minimized.

Copy link
Owner Author

@mikaello mikaello commented Sep 4, 2020

Just a question it’s possible to change the key name ? Exemples mix it with a String, convert it into number (for exemple is the value is a time stamp).

Sure, just change how the value variable is generated:

const groupBy = keys => array =>
  array.reduce((objectsByKeyValue, obj) => {
+   const value = someCustomGroupByKeyGenerator(keys.map(key => obj[key])); // Add your custom generator for keys here
    objectsByKeyValue[value] = (objectsByKeyValue[value] || []).concat(obj);
    return objectsByKeyValue;
  }, {});

The default is to just concatenate with -.

@DYW972

This comment has been minimized.

Copy link

@DYW972 DYW972 commented Sep 8, 2020

Just a question it’s possible to change the key name ? Exemples mix it with a String, convert it into number (for exemple is the value is a time stamp).

Sure, just change how the value variable is generated:

const groupBy = keys => array =>
  array.reduce((objectsByKeyValue, obj) => {
+   const value = someCustomGroupByKeyGenerator(keys.map(key => obj[key])); // Add your custom generator for keys here
    objectsByKeyValue[value] = (objectsByKeyValue[value] || []).concat(obj);
    return objectsByKeyValue;
  }, {});

The default is to just concatenate with -.

Yay !! Thank you very much ! I will make something with it and come back with the result.

@shravanivish

This comment has been minimized.

Copy link

@shravanivish shravanivish commented Aug 11, 2021

hi, can you please help

const arr = [
	{ Id: "001", qty: 1 },
	{ Id: "002", qty: 2 },
	{ Id: "001", qty: 2 },
	{ Id: "003", qty: 4 },
];

const result = [];
arr.reduce(function (res, value) {
	if (!res[value.Id]) {
		res[value.Id] = { Id: value.Id, qty: 0 };
		result.push(res[value.Id]);
	}
	res[value.Id].qty += value.qty;
	return res;
}, {});
console.log(result);
// Output:
// [ 
//   { Id: '001', qty: 3 }, 
//   { Id: '002', qty: 2 }, 
//   { Id: '003', qty: 4 } 
// ]

but, I need output as

{
"001":3,
"002":2,
"003":4
}
@mikaello

This comment has been minimized.

Copy link
Owner Author

@mikaello mikaello commented Aug 11, 2021

@shravanivish

hi, can you please help

This does not look related to the group by function in this gist, please write unrelated questions in other places that are more appropriate, e.g. Stackoverflow.

BTW, your problem could be fixed by using another reduce, or by modifying your existing. But please follow this another place.

@shravanivish

This comment has been minimized.

Copy link

@shravanivish shravanivish commented Aug 12, 2021

@shravanivish

hi, can you please help

This does not look related to the group by function in this gist, please write unrelated questions in other places that are more appropriate, e.g. Stackoverflow.

BTW, your potsticker could be fixed by using another reduce, or by modifying your existing. But please follow this another place.

Thank You!

@melissapalmer

This comment has been minimized.

Copy link

@melissapalmer melissapalmer commented Sep 21, 2021

@mikaello thanks for this, its really helpful!! Would it be possible to group nested values within an array?

For example if we had

const cars = [
  { brand: 'Audi', produced: '2016', color: 'black', drivers: [{ email: "Sebastian.Vettel@cars.com", firstName: "Sebastian", lastName: "Vettel" }, { email: "Lewis.Hamilton@cars.com", firstName: "Lewis", lastName: "Hamilton" }] },
  { brand: 'Audi', produced: '2017', color: 'white', drivers: [{ email: "Lewis.Hamilton@cars.com", firstName: "Lewis", lastName: "Hamilton" }] },
  { brand: 'Ford', produced: '2016', color: 'red', drivers: [{ email: "Lewis.Hamilton@cars.com", firstName: "Lewis", lastName: "Hamilton" }] },
  { brand: 'Ford', produced: '2016', color: 'white', drivers: [{ email: "Fernando.Alonso@cars.com", firstName: "Fernando", lastName: "Alonso" }] },
  { brand: 'Peugot', produced: '2018', color: 'white', drivers: [{ email: "Fernando.Alonso@cars.com", firstName: "Fernando", lastName: "Alonso" }, { email: "Lewis.Hamilton@cars.com", firstName: "Lewis", lastName: "Hamilton" }] }
];

Can we group by drivers.email? So result would that Lewis.Hamilton is associated with 4 cars?
eg:

{
  "Sebastian.Vettel@cars.com":
    [
      {"brand":"Audi","produced":"2016","color":"black","drivers": {"email":"Sebastian.Vettel@cars.com","firstName":"Sebastian","lastName":"Vettel"},{"email":"Lewis.Hamilton@cars.com","firstName":"Lewis","lastName":"Hamilton"}]}
    ],
  "Lewis.Hamilton@cars.com":
    [
      {"brand":"Audi","produced":"2016","color":"black","drivers":[{"email":"Sebastian.Vettel@cars.com","firstName":"Sebastian","lastName":"Vettel"},{"email":"Lewis.Hamilton@cars.com","firstName":"Lewis","lastName":"Hamilton"}]},
      {"brand":"Audi","produced":"2017","color":"white","drivers":[{"email":"Lewis.Hamilton@cars.com","firstName":"Lewis","lastName":"Hamilton"}]},
      {"brand":"Ford","produced":"2016","color":"red","drivers":[{"email":"Lewis.Hamilton@cars.com","firstName":"Lewis","lastName":"Hamilton"}]},             
      {"brand":"Peugot","produced":"2018","color":"white","drivers":[{"email":"Fernando.Alonso@cars.com","firstName":"Fernando","lastName":"Alonso"},{"email":"Lewis.Hamilton@cars.com","firstName":"Lewis","lastName":"Hamilton"}]}
    ],
  "Fernando.Alonso@cars.com":
    [
      {"brand":"Ford","produced":"2016","color":"white","drivers":[{"email":"Fernando.Alonso@cars.com","firstName":"Fernando","lastName":"Alonso"}]},      {"brand":"Peugot","produced":"2018","color":"white","drivers":[{"email":"Fernando.Alonso@cars.com","firstName":"Fernando","lastName":"Alonso"},{"email":"Lewis.Hamilton@cars.com","firstName":"Lewis","lastName":"Hamilton"}]}
    ]
}
@maciel-82

This comment has been minimized.

Copy link

@maciel-82 maciel-82 commented Oct 5, 2021

is there any way to get the count which group have

Yes, that is possible. The values of a group is just an array, so you could just check the length property of that array:

const groupBy = (keys) => (array) =>
  array.reduce((objectsByKeyValue, obj) => {
    const value = keys.map((key) => obj[key]).join("-");
    objectsByKeyValue[value] = (objectsByKeyValue[value] || []).concat(obj);
    return objectsByKeyValue;
  }, {});

const cars = [
  { brand: "Audi", produced: "2016", color: "black" },
  { brand: "Audi", produced: "2017", color: "white" },
  { brand: "Ford", produced: "2016", color: "red" },
  { brand: "Ford", produced: "2016", color: "white" },
  { brand: "Peugot", produced: "2018", color: "white" },
];

const groupByBrandAndYear = groupBy(["brand", "produced"]);

for (let [groupName, values] of Object.entries(groupByBrandAndYear(cars))) {
  console.log(`${groupName}: ${values.length}`);
}
# console output
Audi-2016: 1
Audi-2017: 1
Ford-2016: 2
Peugot-2018: 1

@mikaello nice work!
BTW is it possible to keep the brand and produced attributes separated in the result, instead of concatenating them?

like this:
{brand: "Audi", produced: 2016, count: 1}
{brand: "Ford", produced: 2016, count: 2}
... and so on.

Thanks

@mikaello

This comment has been minimized.

Copy link
Owner Author

@mikaello mikaello commented Oct 6, 2021

@maciel-82 , sure that is possible. But it may be easier to just modify the result accordingly, e.g.

const brandYearCount = Object
  .entries(groupByBrandAndYear(cars))
  .map(([, value]) =>
    ({
      brand: value[0].brand,
      produced: value[0].produced,
      count: value.length
    }))

console.log(brandYearCount)
# Output
[
 {  brand: "Audi",  count: 1,  produced: "2016"},
 {  brand: "Audi",  count: 1,  produced: "2017"},
 {  brand: "Ford",  count: 2,  produced: "2016"},
 {  brand: "Peugot",  count: 1,  produced: "2018"}
]

See JsFiddle

@maciel-82

This comment has been minimized.

Copy link

@maciel-82 maciel-82 commented Oct 7, 2021

@maciel-82 , sure that is possible. But it may be easier to just modify the result accordingly, e.g.

const brandYearCount = Object
  .entries(groupByBrandAndYear(cars))
  .map(([, value]) =>
    ({
      brand: value[0].brand,
      produced: value[0].produced,
      count: value.length
    }))

console.log(brandYearCount)
# Output
[
 {  brand: "Audi",  count: 1,  produced: "2016"},
 {  brand: "Audi",  count: 1,  produced: "2017"},
 {  brand: "Ford",  count: 2,  produced: "2016"},
 {  brand: "Peugot",  count: 1,  produced: "2018"}
]

See JsFiddle

@mikaello it worked perfectly!!!
Thanks a lot!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment