1

it's like Group objects by multiple properties in array then sum up their values but with nested elements and more complicated. I've been struggling for hours.

I have an array of products:

a product looks like this :

{
    "commissioningDate": "2019-09-27",
    "_product": {
        "_id": "aaa",
        "name": "Installation"
    },
    "zones": [
        {
            "_zone": {
                "_id": "KK",
                "name": "Zone kk"
            },
            "category": "category1",
            "zone_quantity": 6
        }
    ],
    "product_quantity": 3
}

Expected behavior

I made this gist because the example is too long.

Problem

So I have an array of products.

1) products in this array are considered duplicates only if both their commissioningDate and _product._id are the same

2) if many products gets merged into a single product:

  • we need a to sum-up the product_quantity
  • we need to merge the zone array inside the product if possible otherwise add it to the array

3) zones of a merged product in this array are considered duplicates only if both their _zone._id and category are the same

4) if many zones gets merged into a single zone we need a to sum-up the zone_quantity

Amine Da.
  • 1,124
  • 3
  • 14
  • 20

1 Answers1

2

Assume your single product zones always has length 1.

const sample = [
    {
        "commissioningDate": "2019-09-27",
        "_product": {
            "_id": "aaa",
            "name": "Installation"
        },
        "zones": [
            {
                "_zone": {
                    "_id": "KK",
                    "name": "Zone kk"
                },
                "category": "category1",
                "zone_quantity": 6
            }
        ],
        "product_quantity": 3
    },
    {
        "commissioningDate": "2019-09-27",
        "_product": {
            "_id": "aaa",
            "name": "Installation"
        },
        "zones": [
            {
                "_zone": {
                    "_id": "KK",
                    "name": "Zone kk"
                },
                "category": "category2",
                "zone_quantity": 3
            }
        ],
        "product_quantity": 2
    },
    {
        "commissioningDate": "2019-09-27",
        "_product": {
            "_id": "aaa",
            "name": "Installation"
        },
        "zones": [
            {
                "_zone": {
                    "_id": "KK",
                    "name": "Zone kk"
                },
                "category": "category2",
                "zone_quantity": 4
            }
        ],
        "product_quantity": 5
    },
    {
        "commissioningDate": "2019-09-27",
        "_product": {
            "_id": "aaa",
            "name": "Installation"
        },
        "zones": [
            {
                "_zone": {
                    "_id": "CC",
                    "name": "Zone cc"
                },
                "category": "category2",
                "zone_quantity": 6
            }
        ],
        "product_quantity": 1
    },
    {
        "commissioningDate": "2019-09-27",
        "_product": {
            "_id": "bbbb",
            "name": "Installation"
        },
        "zones": [
            {
                "_zone": {
                    "_id": "CC",
                    "name": "Zone cc"
                },
                "category": "category2",
                "zone_quantity": 8
            }
        ],
        "product_quantity": 2
    },
    {
        "commissioningDate": "2019-09-26",
        "_product": {
            "_id": "bbbb",
            "name": "Installation"
        },
        "zones": [
            {
                "_zone": {
                    "_id": "CC",
                    "name": "Zone cc"
                },
                "category": "category2",
                "zone_quantity": 8
            }
        ],
        "product_quantity": 2
    }
]

//reduce initialze value is an empty object
const res = sample.reduce((group, item) => {
  //for each item, generate a key k by combining item commissioningDate and item _product._id seperated //by a comma 
  const k = `${item.commissioningDate},${item._product._id}`;
  
  //check if this key k exists in our object group(which is an empty object when we check the first //item)
  //if it is not in the object, we save the key k and its value which is current item into the object //group
  if(!group[k]) group[k] = Object.assign({}, item);
  
  //if it is in the object already 
  else{
  
  //we sum up current item quantity to the group of this item
    group[k].product_quantity+=item.product_quantity;
    
    //find index of zone in current group zones has the same zone id and category as item's
    for(const itemZone of item.zones){
      const zoneIdx = group[k].zones.findIndex(zone => zone._zone._id === itemZone._zone._id && zone.category === itemZone.category)

      //index is -1, it's not in group zones, we push the zone to group zones array
      if(zoneIdx === -1){
        group[k].zones.push(itemZone)
      }
      //in group zones, we sum up zone_quantity
      else{
        group[k].zones[zoneIdx].zone_quantity += itemZone.zone_quantity
      }
    }
    
  }
  //update current group
  return group
}, {})

//recall keys are our custom identifier for different groups of items, values are actually groups of //items, so we only need to get values from group object
console.log(Object.values(res))
Colin
  • 1,195
  • 7
  • 10
  • thank you, i'm gonna test this and try to understand it. but most of the time my single product zones has length more than 1 – Amine Da. Apr 28 '20 at 01:53
  • @AmineDa. can you add example of that as well ? – Code Maniac Apr 28 '20 at 01:58
  • also i'm using es5 – Amine Da. Apr 28 '20 at 01:58
  • @AmineDa. you can just go to babel.io and paste es6 code and you will get a es5 version, that's not a issue. – Code Maniac Apr 28 '20 at 01:59
  • @CodeManiac sure will do that – Amine Da. Apr 28 '20 at 02:00
  • Just add some comments step by step to the code snippet, hope it helps. If you are using es5, it will be similar, you can use object.assign instead of spread, and arrow function can easily be replaced by function(). – Colin Apr 28 '20 at 02:02
  • 1
    If you have zone length more than 1, it will be similar, just iterate items zones and compare to current group zones. – Colin Apr 28 '20 at 02:04
  • @CodeManiac gist updated! it's the same principle . merge the zone who has the same id and category if their product has the same id and date. – Amine Da. Apr 28 '20 at 02:05
  • @CodeManiac most of the time when we merge products we just concat zones arrays of the merged products unless they have the same zone.id and category – Amine Da. Apr 28 '20 at 02:06
  • 1
    @Colin the only thing you need to change is `findIndex` and `group[k].zones.push(item.zones[0])` this part, in case there are multiple you need to use `filter` and then check length for whether it found or not – Code Maniac Apr 28 '20 at 02:06
  • @CodeManiac can you plz update the code it's 3am in here. I need it for tomorrow. I've been struggling all night – Amine Da. Apr 28 '20 at 02:08
  • @AmineDa. colin has already updated code, you can check answer. since it already a good answer i don't wanna post other one – Code Maniac Apr 28 '20 at 02:10
  • @CodeManiac I tried babel.io but the conversion has some issues . still some spread operators... and iterators – Amine Da. Apr 28 '20 at 02:22
  • @CodeManiac error inside babel conversion : typeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.") – Amine Da. Apr 28 '20 at 02:25
  • @CodeManiac It won't be multiple since zones in group has unique zone id and category, if there are multiple they are already grouped – Colin Apr 28 '20 at 02:49
  • 1
    @AmineDa. you can use object.assign to copy object, no need to use spread. I think object.assign should be okay with es5 – Colin Apr 28 '20 at 02:51
  • @Colin i'm still trying , = {...item}; would be object.assign(item, {}) ? – Amine Da. Apr 28 '20 at 03:08
  • @AmineDa. I think it's Object.assign({}, item) – Colin Apr 28 '20 at 04:03