javascript | Object grouping
Asked Answered
C

16

61

I have an object. It looks like below:

[
  {
    "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"
  }
]

I would like to group this data by group field and get this object:

var obj = {
    0 = [
    {
       'group'   = 'Technical detals',
       'name'    = 'Display',
       'id'      = '60',
       'value'   = '4'
    },
    {
       'group'   = 'Technical detals',
       'name'    = 'OS',
       'id'      = '37',
       'value'   = 'Apple iOS'
    }],
    1   = [
    {
       'group'   = 'Manufacturer',
       'name'    = 'Manufacturer',
       'id'      = '58',
       'value'   = 'Apple'
    }]
}

How can I group my first object?

Carving answered 14/2, 2014 at 10:11 Comment(2)
Do you really want an object literal? Since your indexes are 0 and 1, wouldn't an array be better?Forsberg
I think it's possible.Carving
F
45

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.

Forsberg answered 14/2, 2014 at 10:21 Comment(2)
How to change 0, 1, etc to name of key, like 'group'Hanuman
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]]); } Bailar
V
92

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.

Vinita answered 15/1, 2019 at 16:46 Comment(0)
F
45

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.

Forsberg answered 14/2, 2014 at 10:21 Comment(2)
How to change 0, 1, etc to name of key, like 'group'Hanuman
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]]); } Bailar
B
22

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"));
.as-console-wrapper { max-height: 100% !important; top: 0; }

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.

Bait answered 10/12, 2018 at 10:52 Comment(2)
Can someone help me in breaking this down: obj => [obj[prop], []]Sapid
@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.Bait
N
18

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/

Neolithic answered 14/2, 2014 at 10:24 Comment(0)
A
15

Use reduce and filter.

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

{
    "Technical detals" = [
    {
       'group'   = 'Technical detals',
       'name'    = 'Display',
       'id'      = '60',
       'value'   = '4'
    },
    {
       'group'   = 'Technical detals',
       'name'    = 'OS',
       'id'      = '37',
       'value'   = 'Apple iOS'
    }],
    "Manufacturer"   = [
    {
       'group'   = 'Manufacturer',
       'name'    = 'Manufacturer',
       'id'      = '58',
       'value'   = 'Apple'
    }]
}
Alduino answered 3/12, 2019 at 22:47 Comment(1)
The complexity is O(n^2) to traditional O(n). If performance is the factor, another approach should be taken into a consideration.Rushing
C
8

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"));
.as-console-wrapper { max-height: 100% !important; top: 0; }

UPDATE 2023

You could take Object.groupBy and take only the values of the object.

This method needs a callback for a grouping value.

({ group }) => group    // take property group and return it

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(Object.groupBy(data, ({ group }) => group));

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Columbuscolumbyne answered 31/3, 2017 at 9:18 Comment(3)
Can you change it to Descending in the function somehow/Axel
@ApoloRadomer, what do you mean with descending? which property?Columbuscolumbyne
When I group by, I can see the JSON sorted (ASC). I guess I have to use reverse to make it descending. @Nina ScholzAxel
A
1

If you are using lodash, you can use groupBy.

It supports both array and object.

Example:

_.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');
// => { '3': ['one', 'two'], '5': ['three'] }
Anticipation answered 27/9, 2017 at 21:41 Comment(0)
P
1

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 = [{'group':'1', 'name':'name1'},
{'group':'2', 'name':'name2'},
{'group':'2', 'name':'name3'},
,{'group':'1', 'name':'name4'}]; 

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

so result would be like:

[ {'group':'1', 'names':['name1', 'name4']},
{'group':'2', 'names':['name2', 'name3']}

Same but with TypeScript and reduce:

export function groupBy2 <T>( key: string, arr: T[]): { group: string, values: T[] }[] {
     return arr.reduce((storage, item) => {
         const a = storage.find(g => g.group === item[key]);
         if (a) { a.values.push(item); }
         else { storage.push({group: item[key], values: [item]}); }
         return storage;
     }, [] as {group: string, values: T[] }[]);
 }
Piggy answered 2/10, 2020 at 11:11 Comment(0)
K
1

Based on Anthony Awuley answer I prepared generic solution for TypeScript!

const groupBy = <T, K extends keyof T>(value: T[], key: K) =>
  value.reduce((acc, curr) => {
    if (acc.get(curr[key])) return acc;
    acc.set(curr[key], value.filter(elem => elem[key] === curr[key]));
    return acc;
  }, new Map<T[K], T[]>());
Kisser answered 19/5, 2021 at 16:37 Comment(0)
E
1

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);
    out[key] ??= [];
    out[key].push(item);
  }
  return out;
};

const tasks = [
  {task: 'Do the laundry', dueTime: "4/27 13:00"},
  {task: 'Do the dishes', dueTime: "4/27 15:30"},
  {task: 'Do homework', dueTime: "4/28 13:30"},
  {task: 'Clean the garage', dueTime: "4/29 14:00"}
];

// Group by the day a task is due
const grouped = groupBy(tasks, (item) => item.dueTime.slice(0, -6));
console.log(grouped);
Eurasian answered 27/4, 2022 at 6:4 Comment(0)
L
1

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.

Laith answered 1/1, 2023 at 4:25 Comment(1)
This is now available but only in a limited set of browsers.Dour
F
0

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");
Frumenty answered 19/9, 2018 at 19:6 Comment(0)
O
0

Job done by this function :

export function ObjGroupBy(list, key) {
  return list.reduce(
    (groups, item) => ({
      ...groups,
      [item[key]]: [...(groups[item[key]] || []), item],
    }),
    {}
  )
}

Obscene answered 28/5, 2022 at 20:58 Comment(0)
T
0

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 :

{
    "Technical detals": [{
        "name":"Display",
        "group":"Technical detals",
        "id":"60",
        "value":"4"
    }, {
        "name":"OS",
        "group":"Technical detals",
        "id":"37",
        "value":"Apple iOS"
  }],
    "Manufacturer": [{
        "name":"Manufacturer",
        "group":"Manufacturer",
        "id":"58",
        "value":"Apple"
    }]
}

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];
}); 
Tindal answered 5/8, 2022 at 6:7 Comment(0)
F
0

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;
}, {});
Feodore answered 15/2, 2023 at 19:5 Comment(0)
S
-3
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));
Stefaniestefano answered 15/1, 2019 at 16:24 Comment(2)
This is code golf: the answer with the least amount of bytes wins.Goodlooking
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.Rhapsodist

© 2022 - 2024 — McMap. All rights reserved.