Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Group array items using object

Tags:

javascript

People also ask

How do you group an array of objects using a key?

The most efficient method to group by a key on an array of objects in js is to use the reduce function. The reduce() method executes a reducer function (that you provide) on each element of the array, resulting in a single output value.

How do I group an array of objects in JavaScript?

The group() method executes the callbackFn function once for each index of the array, returning a string (or value that can be coerced to a string) indicating the group of the element. A new property and array is created in the result object for each unique group name that is returned by the callback.

Can objects be used in arrays?

Yes, since objects are also considered as datatypes (reference) in Java, you can create an array of the type of a particular class and, populate it with instances of that class.

How do I merge an array of objects into one?

Use the Array. We can use the JavaScript array reduce method to combine objects in an array into one object. We have the arr array which we want to combine into one object. To do that, we call reduce with a callback that returns an object with obj spread into the returned object. And we add the item.


Start by creating a mapping of group names to values. Then transform into your desired format.

var myArray = [
    {group: "one", color: "red"},
    {group: "two", color: "blue"},
    {group: "one", color: "green"},
    {group: "one", color: "black"}
];

var group_to_values = myArray.reduce(function (obj, item) {
    obj[item.group] = obj[item.group] || [];
    obj[item.group].push(item.color);
    return obj;
}, {});

var groups = Object.keys(group_to_values).map(function (key) {
    return {group: key, color: group_to_values[key]};
});

var pre = document.createElement("pre");
pre.innerHTML = "groups:\n\n" + JSON.stringify(groups, null, 4);
document.body.appendChild(pre);

Using Array instance methods such as reduce and map gives you powerful higher-level constructs that can save you a lot of the pain of looping manually.


First, in JavaScript it's generally not a good idea to iterate over arrays using for ... in. See Why is using "for...in" with array iteration a bad idea? for details.

So you might try something like this:

var groups = {};
for (var i = 0; i < myArray.length; i++) {
  var groupName = myArray[i].group;
  if (!groups[groupName]) {
    groups[groupName] = [];
  }
  groups[groupName].push(myArray[i].color);
}
myArray = [];
for (var groupName in groups) {
  myArray.push({group: groupName, color: groups[groupName]});
}

Using the intermediary groups object here helps speed things up because it allows you to avoid nesting loops to search through the arrays. Also, because groups is an object (rather than an array) iterating over it using for ... in is appropriate.

Addendum

FWIW, if you want to avoid duplicate color entries in the resulting arrays you could add an if statement above the line groups[groupName].push(myArray[i].color); to guard against duplicates. Using jQuery it would look like this;

if (!$.inArray(myArray[i].color, groups[groupName])) {
  groups[groupName].push(myArray[i].color);
}

Without jQuery you may want to add a function that does the same thing as jQuery's inArray:

Array.prototype.contains = function(value) {
  for (var i = 0; i < this.length; i++) {
    if (this[i] === value)
      return true;
  }
  return false;
}

and then use it like this:

if (!groups[groupName].contains(myArray[i].color)) {
  groups[groupName].push(myArray[i].color);
}

Note that in either case you are going to slow things down a bit due to all the extra iteration, so if you don't need to avoid duplicate color entries in the result arrays I would recommend avoiding this extra code. There


Use lodash's groupby method

Creates an object composed of keys generated from the results of running each element of collection thru iteratee. The order of grouped values is determined by the order they occur in collection. The corresponding value of each key is an array of elements responsible for generating the key. The iteratee is invoked with one argument: (value).

So with lodash you can get what you want in a single line. See below

let myArray = [
  {group: "one", color: "red"},
  {group: "two", color: "blue"},
  {group: "one", color: "green"},
  {group: "one", color: "black"},
]
let grouppedArray=_.groupBy(myArray,'group')
console.log(grouppedArray)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>

Using ES6, this can be done quite nicely using .reduce() with a Map as the accumulator, and then using Array.from() with its mapping function to map each grouped map-entry to an object:

const arr = [{"group":"one","color":"red"},{"group":"two","color":"blue"},{"group":"one","color":"green"},{"group":"one","color":"black"}];

const res = Array.from(arr.reduce((m, {group, color}) => 
    m.set(group, [...(m.get(group) || []), color]), new Map
  ), ([group, color]) => ({group, color})
);

console.log(res);

If you have additional properties in your objects other than just group and color, you can take a more general approach by setting a grouped object as the map's values like so:

const arr = [{"group":"one","color":"red"},{"group":"two","color":"blue"},{"group":"one","color":"green"},{"group":"one","color":"black"}];

const groupAndMerge = (arr, groupBy, mergeInto) => 
  Array.from(arr.reduce((m, o) => {
    const curr = m.get(o[groupBy]);
    return m.set(o[groupBy], {...o, [mergeInto]: [...(curr && curr[mergeInto] || []), o[mergeInto]]});
  }, new Map).values());

console.log(groupAndMerge(arr, 'group', 'color'));

If you can support optional chaining and the nullish coalescing operator (??), you can simplify the above method to the following:

const arr = [{"group":"one","color":"red"},{"group":"two","color":"blue"},{"group":"one","color":"green"},{"group":"one","color":"black"}];
const groupAndMerge = (arr, groupBy, mergeWith) =>
  Array.from(arr.reduce((m, o) => m.set(o[groupBy], {...o, [mergeWith]: [...(m.get(o[groupBy])?.[mergeWith] ?? []), o[mergeWith]]}), new Map).values());

console.log(groupAndMerge(arr, 'group', 'color'));

One option is:

var res = myArray.reduce(function(groups, currentValue) {
    if ( groups.indexOf(currentValue.group) === -1 ) {
      groups.push(currentValue.group);
    }
    return groups;
}, []).map(function(group) {
    return {
        group: group,
        color: myArray.filter(function(_el) {
          return _el.group === group;
        }).map(function(_el) { return _el.color; })
    }
});

http://jsfiddle.net/dvgwodxq/