23

How would one take a JavaScript array of objects, such as

objArr = [
    {key:"Mon Sep 23 2013 00:00:00 GMT-0400", val:42},
    {key:"Mon Sep 24 2013 00:00:00 GMT-0400", val:78},
    {key:"Mon Sep 25 2013 00:00:00 GMT-0400", val:23},
    {key:"Mon Sep 23 2013 00:00:00 GMT-0400", val:54} // <- duplicate key
]

and merge duplicate keys by summing the values?

In order to get something like this:

reducedObjArr = [
    {key:"Mon Sep 23 2013 00:00:00 GMT-0400", val:96},
    {key:"Mon Sep 24 2013 00:00:00 GMT-0400", val:78},
    {key:"Mon Sep 25 2013 00:00:00 GMT-0400", val:23}
]

I have tried iterating and adding to a new array, but this didn't work:

var reducedObjArr = [];
var item = null, key = null;
for(var i=0; i<objArr.length; i++) {
    item = objArr[i];
    key = Object.keys(item)[0];
    item = item[key];

    if(!result[key]) {
        result[key] = item;
    } else {
       result[key] += item;
    }
}a
adiga
  • 34,372
  • 9
  • 61
  • 83
AlecPerkey
  • 659
  • 2
  • 11
  • 21
  • Why are you doing `key = Object.keys(item)[0]; item=item[key];` ? You already know the name is `key`, so just do `item.key` or `objArr[i].key`. Also, using the `[0]` index won't necessarily always give you the same property. – user2736012 Oct 07 '13 at 19:50
  • Thanks lookinig for same ,, – Soumya Gangamwar Oct 30 '20 at 11:57
  • Will it work without quoting the strings, e.g. `Mon Sep 23 2013 00:00:00 GMT-0400`? – Peter Mortensen May 06 '22 at 12:28
  • A TypeScript alternative is discussed in [Group by and sum on multiple keys maintaining type safety](https://stackoverflow.com/q/76272301/8929855) – swimmer May 23 '23 at 17:14

13 Answers13

17

Rather than using a for loop and pushing values, you can directly use map and reduce:

let objArr = [
  {key: 'Mon Sep 23 2013 00:00:00 GMT-0400', val: 42},
  {key: 'Mon Sep 24 2013 00:00:00 GMT-0400', val: 78},
  {key: 'Mon Sep 25 2013 00:00:00 GMT-0400', val: 23},
  {key: 'Mon Sep 23 2013 00:00:00 GMT-0400', val: 54}
];

// first, convert data into a Map with reduce
let counts = objArr.reduce((prev, curr) => {
  let count = prev.get(curr.key) || 0;
  prev.set(curr.key, curr.val + count);
  return prev;
}, new Map());

// then, map your counts object back to an array
let reducedObjArr = [...counts].map(([key, value]) => {
  return {key, value}
})

console.log(reducedObjArr);
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Hamms
  • 5,016
  • 21
  • 28
  • Can a seperate count be also added as well using map/reduce. Example : 23rd sep, count will be 2, for 24th & 25 count will be 1. – Gunjan Anshul Dec 10 '20 at 09:03
  • Yep! You'd just do something like `count + 1` instead of `curr.val + count` in the `prev.set` call – Hamms Dec 10 '20 at 23:03
15

You should be assigning each object not found to the result with its .key property.

If it is found, then you need to add its .val.

var temp = {};
var obj = null;
for(var i=0; i < objArr.length; i++) {
   obj=objArr[i];

   if(!temp[obj.key]) {
       temp[obj.key] = obj;
   } else {
       temp[obj.key].val += obj.val;
   }
}
var result = [];
for (var prop in temp)
    result.push(temp[prop]);

Also, part of the problem was that you were reusing the item variable to reference the value of .key, so you lost reference to the object.

user2736012
  • 3,543
  • 16
  • 13
11

Simpler reduce than posted elsewhere since it does not use a Map element

const objArr = [
{key:"Mon Sep 23 2013 00:00:00 GMT-0400", val:42},
{key:"Mon Sep 24 2013 00:00:00 GMT-0400", val:78},
{key:"Mon Sep 25 2013 00:00:00 GMT-0400", val:23},
{key:"Mon Sep 23 2013 00:00:00 GMT-0400", val:54}];

const output = objArr.reduce((accumulator, cur) => {
  let date = cur.key;
  let found = accumulator.find(elem => elem.key === date)
  if (found) found.val += cur.val;
  else accumulator.push(cur);
  return accumulator;
}, []);

console.log(output)
mplungjan
  • 169,008
  • 28
  • 173
  • 236
  • Still, an explanation would be in order. E.g., in what way is it simpler? What is the idea/gist? From [the Help Center](https://stackoverflow.com/help/promotion): *"...always explain why the solution you're presenting is appropriate and how it works"*. Please respond by [editing (changing) your answer](https://stackoverflow.com/posts/58951753/edit), not here in comments (***without*** "Edit:", "Update:", or similar - the answer should appear as if it was written today). – Peter Mortensen May 06 '22 at 12:51
  • It’s not using a map. Updated. It is just simpler code. – mplungjan May 06 '22 at 13:17
5

You could use a hash table for the grouping by key.

var array = [{ key: 'Mon Sep 23 2013 00:00:00 GMT-0400', val: 42 }, { key: 'Mon Sep 24 2013 00:00:00 GMT-0400', val: 78 }, { key: 'Mon Sep 25 2013 00:00:00 GMT-0400', val: 23 }, { key: 'Mon Sep 23 2013 00:00:00 GMT-0400', val: 54}],
    grouped = [];

array.forEach(function (o) {
    if (!this[o.key]) {
        this[o.key] = { key: o.key, val: 0 };
        grouped.push(this[o.key]);
    }
    this[o.key].val += o.val;
}, Object.create(null));

console.log(grouped);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Another approach is to collect all key/value pairs in a Map and format the final array with Array.from and a callback for the objects.

var array = [{ key: 'Mon Sep 23 2013 00:00:00 GMT-0400', val: 42 }, { key: 'Mon Sep 24 2013 00:00:00 GMT-0400', val: 78 }, { key: 'Mon Sep 25 2013 00:00:00 GMT-0400', val: 23 }, { key: 'Mon Sep 23 2013 00:00:00 GMT-0400', val: 54 }],
    grouped = Array.from(
        array.reduce((m, { key, val }) => m.set(key, (m.get(key) || 0) + val), new Map),
        ([key, val]) => ({ key, val })
    );

console.log(grouped);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • how to add multiple keys? – Len Aug 20 '18 at 02:55
  • @klent, replace `val` in `this[o.key].val += o.val;` with your property to add. – Nina Scholz Aug 20 '18 at 08:30
  • Sorry, I've meant how to add another condition before it adds for example if their's another property called 'category'. I want to check key and category. – Len Aug 20 '18 at 08:40
  • 1
    take an array for the keys to group, like `groups = ['foo', 'bar']`, inside the `forEach` use `var key = groups.map(k => o[k]).join('|');` and then use `this[key]` instead of `this[o.key]`. maybe omit `this` at all and use a global variable for the object for grouping. – Nina Scholz Aug 20 '18 at 08:46
  • how to sum same values in object if my obj something like this ```obj = [ { menu: "apple", amount: 3}, { menu: "apple", amount: 1}, {menu: "melon", amount: 10}, {menu: "stawberry", amount: 7} ];``` i want the ouput is ```[ {menu: apple, total: 4}, {menu: melon, total: 10}, and.......... ]``` @NinaScholz – Zum Dummi Jan 15 '19 at 09:01
  • @ZumDummi, you need to reduce the array by finding same `menu` properties and update `amount`. – Nina Scholz Jan 15 '19 at 09:10
  • can we just use loops, because reduce and some magic functions not allow at my school for now:D we learn old style method:D – Zum Dummi Jan 15 '19 at 09:16
  • in this case, you better take an object as hash table for the object for accumulation and check against it, if not exists. – Nina Scholz Jan 15 '19 at 09:22
  • The second approach doesn't seem to work. Values are NaN. – antonavy Feb 04 '19 at 22:52
  • Can a seperate count be also added as well using map/reduce. Example : 23rd sep, count will be 2, for 24th & 25 count will be 1. – Gunjan Anshul Dec 10 '20 at 09:04
  • How can we use a property of the key if the key is an object itself instead of being a string inside the array data. For example instead of accessing the 'key' in "var array" accessing 'key.username'? – 06serseri Jul 29 '21 at 11:18
3

var objArr = [
{key:"Mon Sep 23 2013 00:00:00 GMT-0400", val:42},
{key:"Mon Sep 24 2013 00:00:00 GMT-0400", val:78},
{key:"Mon Sep 25 2013 00:00:00 GMT-0400", val:23},
{key:"Mon Sep 23 2013 00:00:00 GMT-0400", val:54}]

var targetObj = {};
for (var i = 0; i < objArr.length; i++) {
  if (!targetObj.hasOwnProperty(objArr[i].key)) {
    targetObj[objArr[i].key] = 0;
  }
  targetObj[objArr[i].key] += objArr[i].val;
}

console.log(targetObj);
mplungjan
  • 169,008
  • 28
  • 173
  • 236
Explosion Pills
  • 188,624
  • 52
  • 326
  • 405
2

One can use Array#reduce with an object to store the values for each key.

let arr = [{key:'Mon Sep 23 2013 00:00:00 GMT-0400', val:42},{key:'Mon Sep 24 2013 00:00:00 GMT-0400', val:78},{key:'Mon Sep 25 2013 00:00:00 GMT-0400', val:23},{key:'Mon Sep 23 2013 00:00:00 GMT-0400', val:54}];
let res = Object.values(arr.reduce((acc, curr)=>{
  (acc[curr.key] = acc[curr.key] || {key: curr.key, val: 0}).val += curr.val;
  return acc;
}, {}));
console.log(res);

In newer browsers, logical nullish assignment can be used.

let arr = [{key:'Mon Sep 23 2013 00:00:00 GMT-0400', val:42},{key:'Mon Sep 24 2013 00:00:00 GMT-0400', val:78},{key:'Mon Sep 25 2013 00:00:00 GMT-0400', val:23},{key:'Mon Sep 23 2013 00:00:00 GMT-0400', val:54}];
let res = Object.values(arr.reduce((acc, curr)=>{
  (acc[curr.key] ??= {key: curr.key, val: 0}).val += curr.val;
  return acc;
}, {}));
console.log(res);
Unmitigated
  • 76,500
  • 11
  • 62
  • 80
1

Try this. It should help.

var arr1 = [
  { name: 'besart', value: 12 },
  { name: 'astrit', value: 10 },
  { name: 'astrit', value: 10 },
  { name: 'besar', value: 18 },
  { name: 'besar', value: 3 },
  { name: 'astrit', value: 3 },
  { name: 'besart', value: 3 },
  { name: 'besart', value: 10 },
  { name: 'besar', value: 0 },
];
var arr2 = [];
var emri = "";
var value = 0;
for (var i = 0; i < arr1.length; i++) {
  emri = arr1[0].name;
  value += arr1[0].value;
  for (var j = 1; j < arr1.length; j++) {
    if (emri == arr1[j].name) {
      value += arr1[j].value;
      arr1.splice(j, 1);
      j--;
    }
  }
  arr1.splice(0, 1);
  arr2[i] = {
    name: emri,
    value: value
  };
  value = 0;
}
console.log(arr2);

Below is another solution that uses only one loop (a while loop):

var arr1 = [
  { name: 'besart', value: 12 },
  { name: 'astrit', value: 10 },
  { name: 'astrit', value: 10 },
  { name: 'besar', value: 18 },
  { name: 'besar', value: 3 },
  { name: 'astrit', value: 3 },
  { name: 'besart', value: 3 },
  { name: 'besart', value: 10 },
  { name: 'besar', value: 0 },
];

var arr2 = [];
var emri = "";
var value = 0;
var i = 1;
var j = 0;

while (arr1.length != 0) {
  emri = arr1[0].name;
  if (emri == arr1[i].name) {
    value += arr1[i].value;
    arr1.splice(i, 1);
    i--;
  }
  i++;
  if (i == arr1.length) {
    value += arr1[0].value;
    i = 1;
    arr2[j] = {
      name: emri,
      value: value
    };
    j++;
    value = 0;
    arr1.splice(0, 1);
  }
}
console.log(arr2)
mplungjan
  • 169,008
  • 28
  • 173
  • 236
besar
  • 19
  • 2
  • Where did you copy this from? The input data is from [Astrit Spanca's answer](https://stackoverflow.com/questions/19233283/sum-javascript-object-propertya-values-with-the-same-object-propertyb-in-an-arra/57365002#57365002). Does it even answer the question? What is the relation between some of the input data and your user name? – Peter Mortensen May 06 '22 at 12:46
  • In any case, an explanation would be in order. E.g., what is the idea/gist? From [the Help Center](https://stackoverflow.com/help/promotion): *"...always explain why the solution you're presenting is appropriate and how it works"*. Please respond by [editing (changing) your answer](https://stackoverflow.com/posts/57372407/edit), not here in comments (***without*** "Edit:", "Update:", or similar - the answer should appear as if it was written today). – Peter Mortensen May 06 '22 at 12:49
  • OK, the OP has left the building (*"Last seen more than 2 years ago"*). There is not much value in this answer. – Peter Mortensen May 06 '22 at 12:49
0

Here is an alternative for you, but similar to that of Explosion Pills. It reuses the original array rather than creating a new one or a different object. The sort may not be necessary and will slow things down a little, but it could be removed.

JavaScript

function reduceMyObjArr(arr) {
    var temp = {},
        index;

    for (index = arr.length - 1; index >= 0; index -= 1) {
        key = arr[index].key;
        if (temp.hasOwnProperty(key)) {
            arr[temp[key]].val += arr[index].val;
            arr.splice(index, 1);
        } else {
            temp[key] = index;
        }
    }

    arr.sort(function (a, b) {
        if (a.key === b.key) {
            return 0;
        }

        if (a.key < b.key) {
            return -1;
        }

        return 1;
    });

    return arr;
}

var myObjArr = [{
    key: "Mon Sep 23 2013 00: 00: 00 GMT - 0400",
    val: 42
}, {
    key: "Mon Sep 24 2013 00: 00: 00 GMT - 0400",
    val: 78
}, {
    key: "Mon Sep 25 2013 00: 00: 00 GMT - 0400",
    val: 23
}, {
    key: "Mon Sep 23 2013 00: 00: 00 GMT - 0400",
    val: 54
}];

reduceMyObjArr(myObjArr);

console.log(myObjArr);

jsFiddle

And a jsperf that compares this (with and without the sort) against the accepted answer. You can improve the performance test by extending the data set.

mplungjan
  • 169,008
  • 28
  • 173
  • 236
Xotic750
  • 22,914
  • 8
  • 57
  • 79
0

You can also try using the JavaScript linq framework which is exactly same as an SQL statement which is given desired output with less written code and effective and found at linq.js.

var objArr =
[
{key:'Mon Sep 23 2013 00:00:00 GMT-0400', val:42},
{key:'Mon Sep 24 2013 00:00:00 GMT-0400', val:78},
{key:'Mon Sep 25 2013 00:00:00 GMT-0400', val:23},
{key:'Mon Sep 23 2013 00:00:00 GMT-0400', val:54}
];


var aggregatedObject = Enumerable.From(objArr)
        .GroupBy("$.key", null,
                 function (key, g) {
                     return {
                       key: key,
                       contributions: g.Sum("$.val")
                     }
        })
        .ToArray();

console.log(aggregatedObject);
<script src="http://cdnjs.cloudflare.com/ajax/libs/linq.js/2.2.0.2/linq.min.js"></script>

Which is pretty easy as compare to looping.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Sanjay Radadiya
  • 1,254
  • 15
  • 22
  • This is confusing. There is also Microsoft's [LINQ](https://en.wikipedia.org/wiki/Language_Integrated_Query) (.NET/C#). Can you [add](https://stackoverflow.com/posts/38295204/edit) a (non-naked) link (URL) to "the JavaScript linq framework"? Please respond by [editing (changing) your answer](https://stackoverflow.com/posts/38295204/edit), not here in comments. (But ***without*** "Edit:", "Update:", or similar - the answer should appear as if it was written today.) – Peter Mortensen May 06 '22 at 12:33
0

Recently I needed a similar implementation and I used a similar solution offered by some guy that used the reduce function.

A few days later I wanted to implement something similar by myself and here is the result.

const users = [
        { id: 1, name: 'ernest', spent: 40 },
        { id: 2, name: 'ernest', spent: 40 },
        { id: 3, name: 'astrit', spent: 22 },
        { id: 4, name: 'astrit', spent: 2956 },
        { id: 5, name: 'astrit', spent: 22 },
        { id: 6, name: 'besart', spent: 40 },
        { id: 7, name: 'besart', spent: 100},
        { id: 8, name: 'besart', spent: 4000 }
    ];

const sum = [];

users.forEach(el => {
    if(sum.length === 0) {
        delete el.id;
        sum.push(el);
    }
    else
    {
        const get = () => {
            for(let i = 0; i < sum.length; i++) {
                if(sum[i].name === el.name) {
                    return { stat: true, id: i };
                }
            }
        }

        let i = get();
        if(i) {
            sum[i.id].spent += el.spent;
        }
        else
        {
            delete el.id;
            sum.push(el);
        }
    }
});

console.log(sum);

Output:

[ { name: 'ernest', spent: 80 }, { name: 'astrit', spent: 3000 }, { name: 'besart', spent: 4140 } ]
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Astrit Spanca
  • 643
  • 5
  • 13
0
function sumGroupBy(array, groupColumn, valueColumn) {
    var res = []
    array.forEach((item) => {
         if(res.map((el) => el[groupColumn]).includes(item[groupColumn])) {
             res.filter((el) => el[groupColumn] == item[groupColumn])[0][valueColumn] += item[valueColumn]
         } else {
             eval(`res.push({${groupColumn}: "${item[groupColumn]}", ${valueColumn}: ${item[valueColumn]} })`)
         }
    });
    return res;
}

const pets = [
    {type:"Dog", age:12},
    {type:"Cat", age:13},
    {type:"Dog", age:6}, 
    {type:"Cat", age:18}
];
    
console.log(sumGroupBy(pets, 'type', 'age' ))
// [ { type: 'Dog', age: 18 }, { type: 'Cat', age: 31 } ]
Behnam
  • 1,039
  • 2
  • 14
  • 39
0

Here is a more general function that you can use to group an array of objects based on a specified key groupKey and calculate the sum of a specified property sumKey:

function groupAndSum(arr, groupKey, sumKey) {
  return Object.values(
    arr.reduce((acc, curr) => {
      const group = curr[groupKey];
      acc[group] = acc[group] || {
        [groupKey]: group,
        [sumKey]: 0
      };
      acc[group][sumKey] += curr[sumKey];
      return acc;
    }, {})
  );
}

Demo:

let objArr = [{ key: 'Mon Sep 23 2013 00:00:00 GMT-0400', val: 42 }, { key: 'Mon Sep 24 2013 00:00:00 GMT-0400', val: 78 }, { key: 'Mon Sep 25 2013 00:00:00 GMT-0400', val: 23 }, { key: 'Mon Sep 23 2013 00:00:00 GMT-0400', val: 54 } ];

function groupAndSum(arr, groupKey, sumKey) {
  return Object.values(
    arr.reduce((acc, curr) => {
      const group = curr[groupKey];
      acc[group] = acc[group] || {
        [groupKey]: group,
        [sumKey]: 0
      };
      acc[group][sumKey] += curr[sumKey];
      return acc;
    }, {})
  );
}
console.log(groupAndSum(objArr, 'key', 'val'))
XMehdi01
  • 5,538
  • 2
  • 10
  • 34
-2
function mergeDuplicatesBy(array, getKey, mergeWith) {
  const buff = {}
  array.forEach(function (arrayElement) {
    const key = getKey(arrayElement)
    const alreadyExistingVal = buff[key]
    if (alreadyExistingVal) {
      buff[key] = mergeWith(alreadyExistingVal, arrayElement)
    } else {
      buff[key] = arrayElement
    }
  })
  return Object.values(buff)
}

mergeDuplicatesBy(
  arr,
  x => x.name,
  (x, y) => ({ name: x.name, foo: x.foo + y.foo })
)
srghma
  • 4,770
  • 2
  • 38
  • 54