[javascript] How to group an array of objects by key

Does anyone know of a (lodash if possible too) way to group an array of objects by an object key then create a new array of objects based on the grouping? For example, I have an array of car objects:

var cars = [
    {
        'make': 'audi',
        'model': 'r8',
        'year': '2012'
    }, {
        'make': 'audi',
        'model': 'rs5',
        'year': '2013'
    }, {
        'make': 'ford',
        'model': 'mustang',
        'year': '2012'
    }, {
        'make': 'ford',
        'model': 'fusion',
        'year': '2015'
    }, {
        'make': 'kia',
        'model': 'optima',
        'year': '2012'
    },
];

I want to make a new array of car objects that's grouped by make:

var cars = {
    'audi': [
        {
            'model': 'r8',
            'year': '2012'
        }, {
            'model': 'rs5',
            'year': '2013'
        },
    ],

    'ford': [
        {
            'model': 'mustang',
            'year': '2012'
        }, {
            'model': 'fusion',
            'year': '2015'
        }
    ],

    'kia': [
        {
            'model': 'optima',
            'year': '2012'
        }
    ]
}

This question is related to javascript arrays object grouping lodash

The answer is


I liked @metakunfu answer, but it doesn't provide the expected output exactly. Here's an updated that get rid of "make" in the final JSON payload.

var cars = [
    {
        'make': 'audi',
        'model': 'r8',
        'year': '2012'
    }, {
        'make': 'audi',
        'model': 'rs5',
        'year': '2013'
    }, {
        'make': 'ford',
        'model': 'mustang',
        'year': '2012'
    }, {
        'make': 'ford',
        'model': 'fusion',
        'year': '2015'
    }, {
        'make': 'kia',
        'model': 'optima',
        'year': '2012'
    },
];

result = cars.reduce((h, car) => Object.assign(h, { [car.make]:( h[car.make] || [] ).concat({model: car.model, year: car.year}) }), {})

console.log(JSON.stringify(result));

Output:

{  
   "audi":[  
      {  
         "model":"r8",
         "year":"2012"
      },
      {  
         "model":"rs5",
         "year":"2013"
      }
   ],
   "ford":[  
      {  
         "model":"mustang",
         "year":"2012"
      },
      {  
         "model":"fusion",
         "year":"2015"
      }
   ],
   "kia":[  
      {  
         "model":"optima",
         "year":"2012"
      }
   ]
}

Agree that unless you use these often there is no need for an external library. Although similar solutions are available, I see that some of them are tricky to follow here is a gist that has a solution with comments if you're trying to understand what is happening.

_x000D_
_x000D_
const cars = [{
  'make': 'audi',
  'model': 'r8',
  'year': '2012'
}, {
  'make': 'audi',
  'model': 'rs5',
  'year': '2013'
}, {
  'make': 'ford',
  'model': 'mustang',
  'year': '2012'
}, {
  'make': 'ford',
  'model': 'fusion',
  'year': '2015'
}, {
  'make': 'kia',
  'model': 'optima',
  'year': '2012'
}, ];

/**
 * Groups an array of objects by a key an returns an object or array grouped by provided key.
 * @param array - array to group objects by key.
 * @param key - key to group array objects by.
 * @param removeKey  - remove the key and it's value from the resulting object.
 * @param outputType - type of structure the output should be contained in.
 */
const groupBy = (
  inputArray,
  key,
  removeKey = false,
  outputType = {},
) => {
  return inputArray.reduce(
    (previous, current) => {
      // Get the current value that matches the input key and remove the key value for it.
      const {
        [key]: keyValue
      } = current;
      // remove the key if option is set
      removeKey && keyValue && delete current[key];
      // If there is already an array for the user provided key use it else default to an empty array.
      const {
        [keyValue]: reducedValue = []
      } = previous;

      // Create a new object and return that merges the previous with the current object
      return Object.assign(previous, {
        [keyValue]: reducedValue.concat(current)
      });
    },
    // Replace the object here to an array to change output object to an array
    outputType,
  );
};

console.log(groupBy(cars, 'make', true))
_x000D_
_x000D_
_x000D_


I'd leave REAL GROUP BY for JS Arrays example exactly the same this task here

_x000D_
_x000D_
const inputArray = [ _x000D_
    { Phase: "Phase 1", Step: "Step 1", Task: "Task 1", Value: "5" },_x000D_
    { Phase: "Phase 1", Step: "Step 1", Task: "Task 2", Value: "10" },_x000D_
    { Phase: "Phase 1", Step: "Step 2", Task: "Task 1", Value: "15" },_x000D_
    { Phase: "Phase 1", Step: "Step 2", Task: "Task 2", Value: "20" },_x000D_
    { Phase: "Phase 2", Step: "Step 1", Task: "Task 1", Value: "25" },_x000D_
    { Phase: "Phase 2", Step: "Step 1", Task: "Task 2", Value: "30" },_x000D_
    { Phase: "Phase 2", Step: "Step 2", Task: "Task 1", Value: "35" },_x000D_
    { Phase: "Phase 2", Step: "Step 2", Task: "Task 2", Value: "40" }_x000D_
];_x000D_
_x000D_
var outObject = inputArray.reduce(function(a, e) {_x000D_
  // GROUP BY estimated key (estKey), well, may be a just plain key_x000D_
  // a -- Accumulator result object_x000D_
  // e -- sequentally checked Element, the Element that is tested just at this itaration_x000D_
_x000D_
  // new grouping name may be calculated, but must be based on real value of real field_x000D_
  let estKey = (e['Phase']); _x000D_
_x000D_
  (a[estKey] ? a[estKey] : (a[estKey] = null || [])).push(e);_x000D_
  return a;_x000D_
}, {});_x000D_
_x000D_
console.log(outObject);
_x000D_
_x000D_
_x000D_


Its also possible with a simple for loop:

 const result = {};

 for(const {make, model, year} of cars) {
   if(!result[make]) result[make] = [];
   result[make].push({ model, year });
 }

I made a benchmark to test the performance of each solution that don't use external libraries.

JSBen.ch

The reduce() option, posted by @Nina Scholz seems to be the optimal one.


There is absolutely no reason to download a 3rd party library to achieve this simple problem, like the above solutions suggest.

The one line version to group a list of objects by a certain key in es6:

const groupByKey = (list, key) => list.reduce((hash, obj) => ({...hash, [obj[key]]:( hash[obj[key]] || [] ).concat(obj)}), {})

The longer version that filters out the objects without the key:

_x000D_
_x000D_
function groupByKey(array, key) {
   return array
     .reduce((hash, obj) => {
       if(obj[key] === undefined) return hash; 
       return Object.assign(hash, { [obj[key]]:( hash[obj[key]] || [] ).concat(obj)})
     }, {})
}


var cars = [{'make':'audi','model':'r8','year':'2012'},{'make':'audi','model':'rs5','year':'2013'},{'make':'ford','model':'mustang','year':'2012'},{'make':'ford','model':'fusion','year':'2015'},{'make':'kia','model':'optima','year':'2012'}];

console.log(groupByKey(cars, 'make'))
_x000D_
_x000D_
_x000D_

NOTE: It appear the original question asks how to group cars by make, but omit the make in each group. So the short answer, without 3rd party libraries, would look like this:

_x000D_
_x000D_
const groupByKey = (list, key, {omitKey=false}) => list.reduce((hash, {[key]:value, ...rest}) => ({...hash, [value]:( hash[value] || [] ).concat(omitKey ? {...rest} : {[key]:value, ...rest})} ), {})

var cars = [{'make':'audi','model':'r8','year':'2012'},{'make':'audi','model':'rs5','year':'2013'},{'make':'ford','model':'mustang','year':'2012'},{'make':'ford','model':'fusion','year':'2015'},{'make':'kia','model':'optima','year':'2012'}];

console.log(groupByKey(cars, 'make', {omitKey:true}))
_x000D_
_x000D_
_x000D_


Here is another solution to it. As requested.

I want to make a new array of car objects that's grouped by make:

function groupBy() {
  const key = 'make';
  return cars.reduce((acc, x) => ({
    ...acc,
    [x[key]]: (!acc[x[key]]) ? [{
      model: x.model,
      year: x.year
    }] : [...acc[x[key]], {
      model: x.model,
      year: x.year
    }]
  }), {})
}

Output:

console.log('Grouped by make key:',groupBy())

_x000D_
_x000D_
var cars = [{
  make: 'audi',
  model: 'r8',
  year: '2012'
}, {
  make: 'audi',
  model: 'rs5',
  year: '2013'
}, {
  make: 'ford',
  model: 'mustang',
  year: '2012'
}, {
  make: 'ford',
  model: 'fusion',
  year: '2015'
}, {
  make: 'kia',
  model: 'optima',
  year: '2012'
}].reduce((r, car) => {

  const {
    model,
    year,
    make
  } = car;

  r[make] = [...r[make] || [], {
    model,
    year
  }];

  return r;
}, {});

console.log(cars);
_x000D_
_x000D_
_x000D_


Grouped Array of Object in typescript with this:

groupBy (list: any[], key: string): Map<string, Array<any>> {
    let map = new Map();
    list.map(val=> {
        if(!map.has(val[key])){
            map.set(val[key],list.filter(data => data[key] == val[key]));
        }
    });
    return map;
});

For cases where key can be null and we want to group them as others

var cars = [{'make':'audi','model':'r8','year':'2012'},{'make':'audi','model':'rs5','year':'2013'},{'make':'ford','model':'mustang','year':'2012'},{'make':'ford','model':'fusion','year':'2015'},{'make':'kia','model':'optima','year':'2012'},
            {'make':'kia','model':'optima','year':'2033'},
            {'make':null,'model':'zen','year':'2012'},
            {'make':null,'model':'blue','year':'2017'},

           ];


 result = cars.reduce(function (r, a) {
        key = a.make || 'others';
        r[key] = r[key] || [];
        r[key].push(a);
        return r;
    }, Object.create(null));

With lodash/fp you can create a function with _.flow() that 1st groups by a key, and then map each group, and omits a key from each item:

_x000D_
_x000D_
const { flow, groupBy, mapValues, map, omit } = _;_x000D_
_x000D_
const groupAndOmitBy = key => flow(_x000D_
  groupBy(key),_x000D_
  mapValues(map(omit(key)))_x000D_
);_x000D_
_x000D_
const cars = [{ make: 'audi', model: 'r8', year: '2012' }, { make: 'audi', model: 'rs5', year: '2013' }, { make: 'ford', model: 'mustang', year: '2012' }, { make: 'ford', model: 'fusion', year: '2015' }, { make: 'kia', model: 'optima', year: '2012' }];_x000D_
_x000D_
const groupAndOmitMake = groupAndOmitBy('make');_x000D_
_x000D_
const result = groupAndOmitMake(cars);_x000D_
_x000D_
console.log(result);
_x000D_
.as-console-wrapper { max-height: 100% !important; top: 0; }
_x000D_
<script src='https://cdn.jsdelivr.net/g/lodash@4(lodash.min.js+lodash.fp.min.js)'></script>
_x000D_
_x000D_
_x000D_


const reGroup = (list, key) => {
    const newGroup = {};
    list.forEach(item => {
        const newItem = Object.assign({}, item);
        delete newItem[key];
        newGroup[item[key]] = newGroup[item[key]] || [];
        newGroup[item[key]].push(newItem);
    });
    return newGroup;
};
const animals = [
  {
    type: 'dog',
    breed: 'puddle'
  },
  {
    type: 'dog',
    breed: 'labradoodle'
  },
  {
    type: 'cat',
    breed: 'siamese'
  },
  {
    type: 'dog',
    breed: 'french bulldog'
  },
  {
    type: 'cat',
    breed: 'mud'
  }
];
console.log(reGroup(animals, 'type'));
const cars = [
  {
      'make': 'audi',
      'model': 'r8',
      'year': '2012'
  }, {
      'make': 'audi',
      'model': 'rs5',
      'year': '2013'
  }, {
      'make': 'ford',
      'model': 'mustang',
      'year': '2012'
  }, {
      'make': 'ford',
      'model': 'fusion',
      'year': '2015'
  }, {
      'make': 'kia',
      'model': 'optima',
      'year': '2012'
  },
];

console.log(reGroup(cars, 'make'));

Prototype version using ES6 as well. Basically this uses the reduce function to pass in an accumulator and current item, which then uses this to build your "grouped" arrays based on the passed in key. the inner part of the reduce may look complicated but essentially it is testing to see if the key of the passed in object exists and if it doesn't then create an empty array and append the current item to that newly created array otherwise using the spread operator pass in all the objects of the current key array and append current item. Hope this helps someone!.

Array.prototype.groupBy = function(k) {
  return this.reduce((acc, item) => ((acc[item[k]] = [...(acc[item[k]] || []), item]), acc),{});
};

const projs = [
  {
    project: "A",
    timeTake: 2,
    desc: "this is a description"
  },
  {
    project: "B",
    timeTake: 4,
    desc: "this is a description"
  },
  {
    project: "A",
    timeTake: 12,
    desc: "this is a description"
  },
  {
    project: "B",
    timeTake: 45,
    desc: "this is a description"
  }
];

console.log(projs.groupBy("project"));

In plain Javascript, you could use Array#reduce with an object

_x000D_
_x000D_
var cars = [{ make: 'audi', model: 'r8', year: '2012' }, { make: 'audi', model: 'rs5', year: '2013' }, { make: 'ford', model: 'mustang', year: '2012' }, { make: 'ford', model: 'fusion', year: '2015' }, { make: 'kia', model: 'optima', year: '2012' }],_x000D_
    result = cars.reduce(function (r, a) {_x000D_
        r[a.make] = r[a.make] || [];_x000D_
        r[a.make].push(a);_x000D_
        return r;_x000D_
    }, Object.create(null));_x000D_
_x000D_
console.log(result);
_x000D_
.as-console-wrapper { max-height: 100% !important; top: 0; }
_x000D_
_x000D_
_x000D_


function groupBy(data, property) {
  return data.reduce((acc, obj) => {
    const key = obj[property];
    if (!acc[key]) {
      acc[key] = [];
    }
    acc[key].push(obj);
    return acc;
  }, {});
}
groupBy(people, 'age');

You can also make use of array#forEach() method like this:

_x000D_
_x000D_
const cars = [{ make: 'audi', model: 'r8', year: '2012' }, { make: 'audi', model: 'rs5', year: '2013' }, { make: 'ford', model: 'mustang', year: '2012' }, { make: 'ford', model: 'fusion', year: '2015' }, { make: 'kia', model: 'optima', year: '2012' }];_x000D_
_x000D_
let newcars = {}_x000D_
_x000D_
cars.forEach(car => {_x000D_
  newcars[car.make] ? // check if that array exists or not in newcars object_x000D_
    newcars[car.make].push({model: car.model, year: car.year})  // just push_x000D_
   : (newcars[car.make] = [], newcars[car.make].push({model: car.model, year: car.year})) // create a new array and push_x000D_
})_x000D_
_x000D_
console.log(newcars);
_x000D_
_x000D_
_x000D_


Here is your very own groupBy function which is a generalization of the code from: https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore

_x000D_
_x000D_
function groupBy(xs, f) {_x000D_
  return xs.reduce((r, v, i, a, k = f(v)) => ((r[k] || (r[k] = [])).push(v), r), {});_x000D_
}_x000D_
_x000D_
const cars = [{ make: 'audi', model: 'r8', year: '2012' }, { make: 'audi', model: 'rs5', year: '2013' }, { make: 'ford', model: 'mustang', year: '2012' }, { make: 'ford', model: 'fusion', year: '2015' }, { make: 'kia', model: 'optima', year: '2012' }];_x000D_
_x000D_
const result = groupBy(cars, (c) => c.make);_x000D_
console.log(result);
_x000D_
_x000D_
_x000D_


Here is a solution inspired from Collectors.groupingBy() in Java:

_x000D_
_x000D_
function groupingBy(list, keyMapper) {_x000D_
  return list.reduce((accummalatorMap, currentValue) => {_x000D_
    const key = keyMapper(currentValue);_x000D_
    if(!accummalatorMap.has(key)) {_x000D_
      accummalatorMap.set(key, [currentValue]);_x000D_
    } else {_x000D_
      accummalatorMap.set(key, accummalatorMap.get(key).push(currentValue));_x000D_
    }_x000D_
    return accummalatorMap;_x000D_
  }, new Map());_x000D_
}
_x000D_
_x000D_
_x000D_

This will give a Map object.

_x000D_
_x000D_
// Usage_x000D_
_x000D_
const carMakers = groupingBy(cars, car => car.make);
_x000D_
_x000D_
_x000D_


You are looking for _.groupBy().

Removing the property you are grouping by from the objects should be trivial if required:

_x000D_
_x000D_
var cars = [{'make':'audi','model':'r8','year':'2012'},{'make':'audi','model':'rs5','year':'2013'},{'make':'ford','model':'mustang','year':'2012'},{'make':'ford','model':'fusion','year':'2015'},{'make':'kia','model':'optima','year':'2012'},];_x000D_
_x000D_
var grouped = _.groupBy(cars, function(car) {_x000D_
  return car.make;_x000D_
});_x000D_
_x000D_
console.log(grouped);
_x000D_
<script src='https://cdn.jsdelivr.net/lodash/4.17.2/lodash.min.js'></script>
_x000D_
_x000D_
_x000D_


As a bonus, you get even nicer syntax with ES6 arrow functions:

const grouped = _.groupBy(cars, car => car.make);

I love to write it with no dependency/complexity just pure simple js.

_x000D_
_x000D_
const mp = {}
const cars = [
  {
    model: 'Imaginary space craft SpaceX model',
    year: '2025'
  },
  {
    make: 'audi',
    model: 'r8',
    year: '2012'
  },
  {
    make: 'audi',
    model: 'rs5',
    year: '2013'
  },
  {
    make: 'ford',
    model: 'mustang',
    year: '2012'
  },
  {
    make: 'ford',
    model: 'fusion',
    year: '2015'
  },
  {
    make: 'kia',
    model: 'optima',
    year: '2012'
  }
]

cars.forEach(c => {
  if (!c.make) return // exit (maybe add them to a "no_make" category)

  if (!mp[c.make]) mp[c.make] = [{ model: c.model, year: c.year }]
  else mp[c.make].push({ model: c.model, year: c.year })
})

console.log(mp)
_x000D_
_x000D_
_x000D_


You can try to modify the object inside the function called per iteration by _.groupBy func. Notice that the source array change his elements!

var res = _.groupBy(cars,(car)=>{
    const makeValue=car.make;
    delete car.make;
    return makeValue;
})
console.log(res);
console.log(cars);

Building on the answer by @Jonas_Wilms if you do not want to type in all your fields:

    var result = {};

    for ( let { first_field, ...fields } of your_data ) 
    { 
       result[first_field] = result[first_field] || [];
       result[first_field].push({ ...fields }); 
    }

I didn't make any benchmark but I believe using a for loop would be more efficient than anything suggested in this answer as well.


Create a method which can be re-used

Array.prototype.groupBy = function(prop) {
      return this.reduce(function(groups, item) {
        const val = item[prop]
        groups[val] = groups[val] || []
        groups[val].push(item)
        return groups
      }, {})
    };

Then below you can group by any criteria

const groupByMake = cars.groupBy('make');
        console.log(groupByMake);

_x000D_
_x000D_
var cars = [_x000D_
    {_x000D_
        'make': 'audi',_x000D_
        'model': 'r8',_x000D_
        'year': '2012'_x000D_
    }, {_x000D_
        'make': 'audi',_x000D_
        'model': 'rs5',_x000D_
        'year': '2013'_x000D_
    }, {_x000D_
        'make': 'ford',_x000D_
        'model': 'mustang',_x000D_
        'year': '2012'_x000D_
    }, {_x000D_
        'make': 'ford',_x000D_
        'model': 'fusion',_x000D_
        'year': '2015'_x000D_
    }, {_x000D_
        'make': 'kia',_x000D_
        'model': 'optima',_x000D_
        'year': '2012'_x000D_
    },_x000D_
];_x000D_
  //re-usable method_x000D_
Array.prototype.groupBy = function(prop) {_x000D_
   return this.reduce(function(groups, item) {_x000D_
  const val = item[prop]_x000D_
  groups[val] = groups[val] || []_x000D_
  groups[val].push(item)_x000D_
  return groups_x000D_
   }, {})_x000D_
 };_x000D_
  _x000D_
 // initiate your groupBy. Notice the recordset Cars and the field Make...._x000D_
  const groupByMake = cars.groupBy('make');_x000D_
  console.log(groupByMake);_x000D_
    _x000D_
    //At this point we have objects. You can use Object.keys to return an array
_x000D_
_x000D_
_x000D_


Just try this one it works fine for me.

let grouped = _.groupBy(cars, 'make');

Note: Using lodash lib, so include it.


Examples related to javascript

need to add a class to an element How to make a variable accessible outside a function? Hide Signs that Meteor.js was Used How to create a showdown.js markdown extension Please help me convert this script to a simple image slider Highlight Anchor Links when user manually scrolls? Summing radio input values How to execute an action before close metro app WinJS javascript, for loop defines a dynamic variable name Getting all files in directory with ajax

Examples related to arrays

PHP array value passes to next row Use NSInteger as array index How do I show a message in the foreach loop? Objects are not valid as a React child. If you meant to render a collection of children, use an array instead Iterating over arrays in Python 3 Best way to "push" into C# array Sort Array of object by object field in Angular 6 Checking for duplicate strings in JavaScript array what does numpy ndarray shape do? How to round a numpy array?

Examples related to object

How to update an "array of objects" with Firestore? how to remove json object key and value.? Cast object to interface in TypeScript Angular 4 default radio button checked by default How to use Object.values with typescript? How to map an array of objects in React How to group an array of objects by key push object into array Add property to an array of objects access key and value of object using *ngFor

Examples related to grouping

Spark difference between reduceByKey vs groupByKey vs aggregateByKey vs combineByKey How to group an array of objects by key using lodash .groupBy. how to add your own keys for grouped output? Group a list of objects by an attribute How can I group data with an Angular filter? How to group subarrays by a column value? Group list by values How to select the first row for each group in MySQL? SVG Positioning

Examples related to lodash

Can't perform a React state update on an unmounted component Angular 4 HttpClient Query Parameters use Lodash to sort array of object by value How to group an array of objects by key Removing object properties with Lodash How to merge two arrays of objects by ID using lodash? Error: EACCES: permission denied Replacing objects in array lodash: mapping array to object Correct way to import lodash