user2644620
user2644620

Reputation: 199

Sum up JSON array items by common key

Im facing a tricky problem in forming a JSON array.

I have a below JSON where products are duplicated, 

    var mainJson = [{
    "product": "pen",
    "quantity": 3
}, {
    "product": "pen",
    "quantity": 3
}, {
    "product": "pencil",
    "quantity": 4
}, {
    "product": "pencil",
    "quantity": 4
}]

now i want to remove the duplicated and i want to add Quantity field and my final output should look like below...

var finalOutput = [{
"product":"pen",
"quantity":6
},{
"product":"pencil",
"quantity":8
}]

Im able to remove the duplicate records with same product name but im not able to concatenate the quantity field at the time of elimination..

Can someone please help me to resolve this?

Thank you in advance

Upvotes: 1

Views: 1204

Answers (4)

Yevhen Horbunkov
Yevhen Horbunkov

Reputation: 15530

You may walk through your source array, using Array.prototype.reduce() and insert into resulting array item, having product value found for the first time, or add current quantity should one already exist:

const mainJson = [{"product":"pen","quantity":3},{"product":"pen","quantity":3},{"product":"pencil","quantity":4},{"product":"pencil","quantity":4}],
      groupped = mainJson.reduce((res,{product,quantity}) => {
        const group = res.find(item => item.product == product)
        group ?
        group.quantity += quantity : 
        res.push({product,quantity})
        return res
      }, [])
    
console.log(groupped)
.as-console-wrapper {min-height: 100%}

EDIT:

Above algorithm (having O(n²) time complexity) will perform nicely when the number of unique product items is relatively small, however for large number of product items, it is reasonable (just like @CameronDowner and @StepUp suggest) to build up a sort of hash map (first pass) with products and respective totals and transform that into array of desired format (second pass) which makes it O(n) time complexity:

const mainJson = [{"product":"pen","quantity":3},{"product":"pen","quantity":3},{"product":"pencil","quantity":4},{"product":"pencil","quantity":4}],
      groupObj = mainJson.reduce((r,{product,quantity}) => 
                  (r[product] = (r[product]||0) + quantity, r), {}),
      group = Object.keys(groupObj).map(key => ({product:key, quantity: groupObj[key]}))

console.log(group)
.as-console-wrapper {min-height: 100%}

Upvotes: 2

StepUp
StepUp

Reputation: 38134

We can use reduce function:

const result = mainJson.reduce( (a, {product, quantity})=> {
   a[product] = a[product] || {product, quantity: 0};
   a[product].quantity += quantity;
   return a;
},{})

An example:

var mainJson = [{
  "product": "pen",
  "quantity": 3
}, {
  "product": "pen",
  "quantity": 3
}, {
  "product": "pencil",
  "quantity": 4
}, {
  "product": "pencil",
  "quantity": 4
}];

const result = mainJson.reduce( (a, {product, quantity})=> {
  a[product] = a[product] || {product, quantity: 0};
  a[product].quantity += quantity;
  return a;
},{})

console.log(Object.values(result));

If you are bothering about performance, then you can use the following solution.

const result = mainJson.reduce( (a, {product, quantity})=> {
  a[product] = a[product] || {product, quantity: 0};
  a[product].quantity += quantity;
  return a;
},{})    

let vals = [];
for (var key in result) {
    if (result.hasOwnProperty(key) ) {
        vals.push(result[key]);
    }
}

console.log(vals);

You can see results at JSBench.me

Upvotes: 1

E. Zacarias
E. Zacarias

Reputation: 647

While the reduce method is quite Javascript, if you prefer a more generic way, you can do it in two steps:

  1. Iterate over your JSON and create a Map with no duplicated items;
  2. Iterate over the Map and get your final JSON.

Take in mind this solution is the hard way, and it is slower than the reduce method. I will not blame you if you keep with the nicer reduce option, I just wanted to point out this more generic way! The result is the same.

Following the steps, we have:

var mainJson = [{"product": "pen","quantity": 3}, {"product": "pen","quantity": 3}, 
{"product": "pencil","quantity": 4}, {"product": "pencil","quantity": 4}];

// Step 1
var mapJson = new Map();
for(let item of mainJson)
{
    if (mapJson.has(item.product))
        mapJson.set(item.product, mapJson.get(item.product) + item.quantity);
    else
        mapJson.set(item.product, item.quantity);
}

// Step 2
var finalJson = [];
for(let item of mapJson)
    finalJson.push({product:item[0], quantity:item[1]});

console.log(finalJson);

Upvotes: 1

Cameron Downer
Cameron Downer

Reputation: 2038

I would do this in two stages. First I would reduce the array into an object, with the product as the key and the quantity as the value.

This handles the duplicates very well because object keys can never be duplicated.

I would then map this, using Object.entries, back to an array in the desired format. Using array destructuring can make this step very clean.

const mainJson = [
  {
    product: "pen",
    quantity: 3
  },
  {
    product: "pen",
    quantity: 3
  },
  {
    product: "pencil",
    quantity: 4
  },
  {
    product: "pencil",
    quantity: 4
  }
];

const productQuantities = mainJson.reduce((acc, curr) => {
  const { product, quantity } = curr;
  const currentValue = acc[product] || 0; // default to zero if not set yet
  return {
    ...acc,
    [product]: currentValue + quantity
  };
}, {});

console.log(productQuantities);

const productQuantitiesArray = Object.entries(
  productQuantities
).map(([product, quantity]) => ({ product, quantity }));

console.log(productQuantitiesArray);

Upvotes: 2

Related Questions