Skip to content

Instantly share code, notes, and snippets.

@mikaello
Forked from JamieMason/group-objects-by-property.md
Last active October 8, 2024 12:41
Show Gist options
  • Save mikaello/06a76bca33e5d79cdd80c162d7774e9c to your computer and use it in GitHub Desktop.
Save mikaello/06a76bca33e5d79cdd80c162d7774e9c to your computer and use it in GitHub Desktop.
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.

@mikaello
Copy link
Author

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
Copy link

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
Copy link

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
Copy link
Author

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
Copy link

@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
Copy link

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: "[email protected]", firstName: "Sebastian", lastName: "Vettel" }, { email: "[email protected]", firstName: "Lewis", lastName: "Hamilton" }] },
  { brand: 'Audi', produced: '2017', color: 'white', drivers: [{ email: "[email protected]", firstName: "Lewis", lastName: "Hamilton" }] },
  { brand: 'Ford', produced: '2016', color: 'red', drivers: [{ email: "[email protected]", firstName: "Lewis", lastName: "Hamilton" }] },
  { brand: 'Ford', produced: '2016', color: 'white', drivers: [{ email: "[email protected]", firstName: "Fernando", lastName: "Alonso" }] },
  { brand: 'Peugot', produced: '2018', color: 'white', drivers: [{ email: "[email protected]", firstName: "Fernando", lastName: "Alonso" }, { email: "[email protected]", firstName: "Lewis", lastName: "Hamilton" }] }
];

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

{
  "[email protected]":
    [
      {"brand":"Audi","produced":"2016","color":"black","drivers": {"email":"[email protected]","firstName":"Sebastian","lastName":"Vettel"},{"email":"[email protected]","firstName":"Lewis","lastName":"Hamilton"}]}
    ],
  "[email protected]":
    [
      {"brand":"Audi","produced":"2016","color":"black","drivers":[{"email":"[email protected]","firstName":"Sebastian","lastName":"Vettel"},{"email":"[email protected]","firstName":"Lewis","lastName":"Hamilton"}]},
      {"brand":"Audi","produced":"2017","color":"white","drivers":[{"email":"[email protected]","firstName":"Lewis","lastName":"Hamilton"}]},
      {"brand":"Ford","produced":"2016","color":"red","drivers":[{"email":"[email protected]","firstName":"Lewis","lastName":"Hamilton"}]},             
      {"brand":"Peugot","produced":"2018","color":"white","drivers":[{"email":"[email protected]","firstName":"Fernando","lastName":"Alonso"},{"email":"[email protected]","firstName":"Lewis","lastName":"Hamilton"}]}
    ],
  "[email protected]":
    [
      {"brand":"Ford","produced":"2016","color":"white","drivers":[{"email":"[email protected]","firstName":"Fernando","lastName":"Alonso"}]},      {"brand":"Peugot","produced":"2018","color":"white","drivers":[{"email":"[email protected]","firstName":"Fernando","lastName":"Alonso"},{"email":"[email protected]","firstName":"Lewis","lastName":"Hamilton"}]}
    ]
}

@maciel-82
Copy link

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
Copy link
Author

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
Copy link

@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!

@Taspee
Copy link

Taspee commented Nov 15, 2022

@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, In this case, how could i get the brand that was produce the most per year?

@aacassandra
Copy link

thanks, its great

@reddo
Copy link

reddo commented Sep 5, 2023

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;
  }, {});

I know this is an old gist, but Is there any way you could help me with the typescript version of this?

@kkg0
Copy link

kkg0 commented Dec 9, 2023

how can i iterate through returned array ?

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