javascript | Object grouping
Reduce is great for situations like this. Given list below is your input data:
const list = [< 'name': 'Display', 'group': 'Technical detals', 'id': '60', 'value': '4' >, < 'name': 'Manufacturer', 'group': 'Manufacturer', 'id': '58', 'value': 'Apple' >, < 'name': 'OS', 'group': 'Technical detals', 'id': '37', 'value': 'Apple iOS' >]; const groups = list.reduce((groups, item) => < const group = (groups[item.group] || []); group.push(item); groups[item.group] = group; return groups; >, <>); console.log(groups);
And if you wanted to be immutable, you could write the reduce like this:
const list = [< 'name': 'Display', 'group': 'Technical detals', 'id': '60', 'value': '4' >, < 'name': 'Manufacturer', 'group': 'Manufacturer', 'id': '58', 'value': 'Apple' >, < 'name': 'OS', 'group': 'Technical detals', 'id': '37', 'value': 'Apple iOS' >]; const groups = list.reduce((groups, item) => (< . groups, [item.group]: [. (groups[item.group] || []), item] >), <>); console.log(groups);
Depending on whether your environment allows the spread syntax.
Try with something like this:
function groupBy(collection, property) < var i = 0, val, index, values = [], result = []; for (; i < collection.length; i++) < val = collection[i][property]; index = values.indexOf(val); if (index >-1) result[index].push(collection[i]); else < values.push(val); result.push([collection[i]]); >> return result; > var obj = groupBy(list, "group");
Keep in mind that Array.prototype.indexOf isn’t defined in IE8 and older, but there are common polyfills for that.
I know this is old but I figured some ppl might stumble upon this and would like to have to grouped value as the key, using the same function just modify the if else part like so: if (index > -1) result[val].push(collection[i]); else < values.push(val); result[val] = []; result[val].push([collection[i]]); >
If you like working with ES6 Map , then this is for you:
function groupBy(arr, prop) < const map = new Map(Array.from(arr, obj =>[obj[prop], []])); arr.forEach(obj => map.get(obj[prop]).push(obj)); return Array.from(map.values()); > const data = [< name: "Display", group: "Technical detals", id: 60, value: 4 >, < name: "Manufacturer", group: "Manufacturer", id: 58, value: "Apple" >, < name: "OS", group: "Technical detals", id: 37, value: "Apple iOS" >]; console.log(groupBy(data, "group"));
The Map instance is created from key/value pairs that are generated from the input array. The keys are the values of the property to group by, and the values are initialised as empty arrays.
Then those arrays are populated. Finally the values of the map (i.e. those populated arrays) are returned.
@AnkitAgarwal, that is a function (arrow function syntax). It takes one argument ( obj ) and returns an array that has two entries. The first entry will have the value obj[prop] (the property value to group by). The second entry is an empty array. This function is passed as callback argument to Array.from , which will call it for every object in arr . The Map constructor can deal with such an array of little pairs, and so new Map will get a key for each group, and the corresponding value will be an empty array for each of those.
If you’re using underscore.js in your application then you can simply do the following:
var groups = _.groupBy(data, 'group'); // data is your initial collection
Or if you prefer not to use any library then you can do it yourself:
var groups = < >; data.forEach(function(item) < var list = groups[item.group]; if(list)< list.push(item); >else < groups[item.group] = [item]; >>);
You can see both examples in action http://jsfiddle.net/nkVu6/3/
lets say your initial array is assigned to data
data.reduce((acc, d) => < if (Object.keys(acc).includes(d.group)) return acc; acc[d.group] = data.filter(g =>g.group === d.group); return acc; >, <>)
this will give you something like
The complexity is O(n^2) to traditional O(n). If performance is the factor, another approach should be taken into a consideration.
You could use a hash table for the groups and Array#forEach for iterating the array.
Then check if the hash exist and if not assign an empty array to it and push it to the result set.
Later push the actual element to the array of the hash.
function groupBy(array, group) < var hash = Object.create(null), result = []; array.forEach(function (a) < if (!hash[a[group]]) < hash[a[group]] = []; result.push(hash[a[group]]); >hash[a[group]].push(a); >); return result; > var data = [< name: "Display", group: "Technical detals", id: 60, value: 4 >, < name: "Manufacturer", group: "Manufacturer", id: 58, value: "Apple" >, < name: "OS", group: "Technical detals", id: 37, value: "Apple iOS" >]; console.log(groupBy(data, "group"));
UPDATE 2022
You could take the possible upcoming Array#groupBy or a polyfill and take only the values of the object.
This methods need a callback for a grouping value.
(< group >) => group // take property group and return it
Array.prototype.groupBy ??= function(callbackfn, thisArg) < const O = Object(this), len = O.length >>> 0; if (typeof callbackfn !== 'function') < throw new TypeError(callbackfn + ' is not a function'); >let k = 0; const groups = <>; while (k < len) < const Pk = Number(k).toString(), kValue = O[Pk], propertyKey = callbackfn.call(thisArg, kValue, Number(k), O); (groups[propertyKey] ??= []).push(kValue); ++k; >return groups; > const data = [< name: "Display", group: "Technical details", id: 60, value: 4 >, < name: "Manufacturer", group: "Manufacturer", id: 58, value: "Apple" >, < name: "OS", group: "Technical details", id: 37, value: "Apple iOS" >], result = Object.values(data.groupBy((< group >) => group)); console.log(result);
When I group by, I can see the JSON sorted (ASC). I guess I have to use reverse to make it descending. @Nina Scholz
If you are using lodash, you can use groupBy .
It supports both array and object.
_.groupBy([6.1, 4.2, 6.3], Math.floor); // => < '4': [4.2], '6': [6.1, 6.3] >// The `_.property` iteratee shorthand. _.groupBy(['one', 'two', 'three'], 'length'); // =>
A bit different way, so we have a plain list of objects, and want to group it per property, but includes all related
const data = [, , , ,]; const list = data.map( i => i.group); const uniqueList = Array.from(new Set(list)); const groups= uniqueList.map( c => < return < group:c, names:[]>; > ); data.forEach( d => < groups.find( g =>g.group === d.group).names.push(d.name); >);
Same but with TypeScript and reduce:
export function groupBy2 ( key: string, arr: T[]): < group: string, values: T[] >[] < return arr.reduce((storage, item) => < const a = storage.find(g =>g.group === itemJavascript group by object); if (a) < a.values.push(item); >else < storage.push(); > return storage; >, [] as []); >
Based on Anthony Awuley answer I prepared generic solution for TypeScript!
const groupBy = (value: T[], key: K) => value.reduce((acc, curr) => < if (acc.get(currJavascript group by object)) return acc; acc.set(currJavascript group by object, value.filter(elem =>elemJavascript group by object === currJavascript group by object)); return acc; >, new Map());
This works using a key-getter so you can dynamically group an array’s objects.
const groupBy = (arr, keyGetter) => < const out = <>; for (let item of arr) < const key = keyGetter(item); outJavascript group by object ??= []; outJavascript group by object.push(item); >return out; >; const tasks = [ , , , ]; // Group by the day a task is due const grouped = groupBy(tasks, (item) => item.dueTime.slice(0, -6)); console.log(grouped);
I tried to use the answer marked as accepted, but noticed that there were elements missing in some groups, depending on the type of property being evaluated. This is a solution derived from that answer:
function groupBy(collection, property) < var i = 0, values = [], result = []; for (i; i < collection.length; i++) < if(values.indexOf(collection[i][property]) === -1) < values.push(collection[i][property]); result.push(collection.filter(function(v) < return v[property] === collection[i][property] >)); > > return result; > var obj = groupBy(list, "group");
Job done by this function :
export function ObjGroupBy(list, key) < return list.reduce( (groups, item) =>(< . groups, [itemJavascript group by object]: [. (groups[itemJavascript group by object] || []), item], >), <> ) >
You can achieve this by using Array.group() method which is just introduced in July 2022 and currently in Experimental stage. I will suggest you to Check the Browser compatibility table carefully before using this in production.
const arr = [ < "name":"Display", "group":"Technical detals", "id":"60", "value":"4" >, < "name":"Manufacturer", "group":"Manufacturer", "id":"58", "value":"Apple" >, < "name":"OS", "group":"Technical detals", "id":"37", "value":"Apple iOS" >]; const result = arr.group((< group >) => group);
Result is :
If you want index key, You can manipulate the result array.
const res = Object.keys(result).forEach((item, index) => < result[index] = result[item]; delete result[item]; >);
Now we have group function for arrays:
Array.prototype.group()
The group() method groups the elements of the calling array according to the string values returned by a provided testing function. The returned object has separate properties for each group, containing arrays with the elements in the group.
In time of writing this answer it’s an experimental feature and it’s not supported by any browser.But it could be considered for later uses or with polyfills or script transpilers.
The first argument passed to reduce is a function that takes two parameters: the accumulator (acc) and the current array item (product). The second argument is the initial value of the accumulator, which in this case is an empty object <>.
Inside the reduce function, we check if there is already an array for the current category (product.category) in the accumulator. If it doesn’t exist, we create a new empty array for that category in the accumulator. Then we add the product to the corresponding array.
const productPerCategory = arr.reduce((acc, product) => < if (!acc[product.group]) < acc[produto.group] = []; >acc[product.group].push(product); return acc; >, <>);
let g = (d,h=<>,r=<>,i=0)=>(d.map(x=>(y=x.group,h[y]?1:(h[y]=++i,r[h[y]-1]=[]),r[h[y]-1].push(x))),r); console.log( g(data) );
let data=[ < "name":"Display", "group":"Technical detals", "id":"60", "value":"4" >, < "name":"Manufacturer", "group":"Manufacturer", "id":"58", "value":"Apple" >, < "name":"OS", "group":"Technical detals", "id":"37", "value":"Apple iOS" >]; let g = (d,h=<>,r=<>,i=0)=>(d.map(x=>(y=x.group,h[y]?1:(h[y]=++i,r[h[y]-1]=[]),r[h[y]-1].push(x))),r); console.log(g(data));
Sadly this is completely unreadable and should never be used in production ready code. The maintainability of this will be atrocious for anyone coming in to make changes to this in the future.
Group objects by property in javascript
@Harley It’s best to show what you’ve tried. Succinct is not as important as showing effort. It’s not just an idle request, either—seeing where you are with it gives us a better shot at creating something exactly at the needed level.
@ErikE Alternatively, this is a nice and simple question that has high googability, with no such nonsense as failed and convoluted attempts of a solution. This is the type of question that will get the most up-votes because its generic and the answer will be as well, ultimately benefitting everyone.
@SSHThis To each his own perspective, though some perspectives may be more in line with reality than others. I think you will find once you participate on this site longer that showing effort is an important part of the community’s values.
8 Answers 8
Assuming the original list is contained in a variable named list :
_ .chain(list) .groupBy('type') .map(function(value, key) < return < type: key, foods: _.pluck(value, 'food') >>) .value();
Whoever needs their code to perform well and avoid doing data query operations on RAM and wants to make use of the database server.
var origArr = [ , , ]; /*[ , ]*/ function transformArr(orig) < var newArr = [], types = <>, i, j, cur; for (i = 0, j = orig.length; i < j; i++) < cur = orig[i]; if (!(cur.type in types)) < types[cur.type] = ; newArr.push(types[cur.type]); > types[cur.type].foods.push(cur.food); > return newArr; > console.log(transformArr(origArr));
Credit goes to @ErikE for improving/reducing my original code to help with redundancy I had 🙂
Suggestion: put newArr.push(types[cur.type]); inside your if block and remove the second for loop entirely.
@ErikE Yeah, I’m already refactoring. I know the double loop isn’t good, so I went back. Hold on, maybe what I’m doing is what you’re saying.
@ErikE I think it took a little more than what you suggested, but I already forget what it used to look like. It seems to work, and doesn’t require 2 loops, so it seems fine 🙂
@ErikE Ahh yes, I’m fooled again. I forget that doing it that way, the new object is pushed by reference (which I didn’t realize), so you can modify the original (in types ) and it will be reflected in newArr . Thanks for pointing that out! Though I’d hardly call my old code awkward. it made sense as a «lookup» to me at least 🙂
Here is a slightly different but more generic version of @Ian’s answer
Caveat: the result is slightly different from the OP requirement, but others like me who stumble on this question might benefit from a more generic answer IMHO
var origArr = [ , , ]; function groupBy(arr, key) < var newArr = [], types = <>, newItem, i, j, cur; for (i = 0, j = arr.length; i < j; i++) < cur = arr[i]; if (!(curJavascript group by object in types)) < types[curJavascript group by object] = < type: curJavascript group by object, data: [] >; newArr.push(types[curJavascript group by object]); > types[curJavascript group by object].data.push(cur); > return newArr; > console.log(groupBy(origArr, 'type'));
Hi there. This is exactly what I’m looking for! Thank you! — I think it would be a good idea to show what console.log prints out.
An ES6 solution to this old question:
Iterate using Array#reduce , and collect the items by group into a Map . Use spread to convert the Map#values back into array:
const data = [ , , , ]; const result = [. data.reduce((hash, < food, type >) => < const current = hash.get(type) || < type, foods: [] >; current.foods.push(< food >); return hash.set(type, current); >, new Map).values()]; console.log(result);
var foods = [ , , ]; var newFoods = _.chain( foods ).reduce(function( memo, food ) < memo[ food.type ] = memo[ food.type ] || []; memo[ food.type ].push( food.food ); return memo; >, <>).map(function( foods, type ) < return < type: type, foods: foods >; >).value();
You can group array of objects by one of fields with Alasql library. This example compact arrays exactly as in your example:
var res = alasql('SELECT type, ARRAY(food) AS foods FROM ? GROUP BY type',[food]);
hi agershum, i tried do what you wrote on an array created fron ajax but wont work result are undefined. But if i try console.log(array[0].property); data in cosolle are ok
hi agershum, i tried do what you wrote on an array created fron ajax but wont work. Result are allways undefined. example: var array=[ < status: undefined student_id: "32" student_name: "Marco Bruno" student_year_id: "2", status: absent >. to 500 record ] But if i try console.log(array[0].property); data in cosolle are ok. I want to count student_id and group by status thanks
You could also use other ES6 Features such as:
function groupArray(arr, groupBy, keepProperty) < let rArr = [], i; arr.forEach(item => < if((i = rArr.findIndex(obj =>obj[groupBy] === item[groupBy])) !== -1) rArr[i][`$s`].push(item[keepProperty]); else rArr.push(< [groupBy]: item[groupBy], [`$s`]: [item[keepProperty]] >); >); return rArr; > groupArray(yourArray, 'type', 'food');
You can achieve this by using Array.group() method along with Array.forEach() which is currently in Experimental stage. I will suggest you to Check the Browser compatibility table carefully before using this in production.
Below code snippet will only work in Firefox Nightly as currently Array.group() does not have support for any other browsers.
// Input array const inventory = [ , , , ]; // Return the object grouped with type property. const result = inventory.group((< type >) => type); // declaring an empty array to store the final results. const arr = []; // Iterating the group object to structure the final result. Object.keys(result).forEach((item, index) => < result[item] = result[item].map((< food >) => food); arr.push(< type: item, foods: result[item] >) >); // Output console.log(arr);