Search code examples
javascriptunderscore.jslodash

Merging array objects with same properies


i am doing one of my front end project and i have a situation where i have to merge/add objects present in the array based on some conditions. Conditions would be

  • Only those Objects having same label should be merged.
  • For objects which has same label, if object 'a' has properties which are present in b object as well, the value has to be added, else simply copy the property.

So my input would be

[
  {
    label: 'label-1',
    published: 1,
    draft: 2,
    id: 'some1'
  },
  {
    label: 'label-1',
    published: 2,
    status: 0,
    draft: 1,
    id: 'some4'
  },
  {
    label: 'label-2',
    published: 1,
    draft: 14,
    id: 'some2'
  },
  {
    label: 'label-2',
    published: 12,
    status: 0,
    draft: 14,
    id: 'some3'
  }
]

and the expect

    [
      {
        label: 'label-1',
        published: 3,
        draft: 4,
        status: 0
      },
{
        label: 'label-2',
        published: 13,
        draft: 28,
        status: 0
      }
    ]

Currently i am using the following code for achieving the same , but find it not tidy . Is there any way this could be achieved easily.

function mapData(data) {
    let groupData = _.groupBy(data, 'label');
    let stackBarData = [];
    Object.keys(groupData).forEach((key) => {
      if (groupData[key] && groupData[key].length > 0) {
        let temp = Array.from(groupData[key]).reduce((a, b) => {
          for (let property in b) {
            if (b.hasOwnProperty(property)) {
              if (property !== 'label' && property !== 'id' && property !== 'Others') {
                a[property] = (a[property] || 0) + b[property];
              } else {
                a[property] = b[property];
              }
            }
          }
          return a;
        }, {});
        stackBarData.push(temp);
      }
    });
    return stackBarData;
  }

Please help.


Solution

  • Here is a pure ES6 function that collects the object values that are numeric, adding them up (which is what you seem to do), per unique label:

    function mapData(data) {
        const grouped = new Map(data.map( ({label}) => [label, { label }] ));
        for (let obj of data) {
            let target = grouped.get(obj.label);
            for (let [key, val] of Object.entries(obj)) {
                if (typeof val === 'number') {
                    target[key] = (target[key] || 0) + val;
                }
            }
        }
        return [...grouped.values()];
    }
    
    // Sample data
    const data = [{label: 'label-1',published: 1,draft: 2,id: 'some1'},{label: 'label-1',published: 2,status: 0,draft: 1,id: 'some4'},{label: 'label-2',published: 1,draft: 14,id: 'some2'},{label: 'label-2',published: 12,status: 0,draft: 14,id: 'some3'}];
    
    console.log(mapData(data));
    .as-console-wrapper { max-height: 100% !important; top: 0; }

    If you have numeric properties that you wanted to exclude, then it might be better to have an explicit set of properties you are interested in:

    const props = new Set(['status', 'published', 'draft']);
    // ... etc
    //
    if (props.has(key)) { 
        target[key] = (target[key] || 0) + val;
    }
    // ...