0

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.

2
  • You have tagged both: underscore.js and lodash. These are 2 different libs. Which one are you using actually? Commented Feb 2, 2018 at 15:42
  • 2
    How is angular related to your issue? Commented Feb 2, 2018 at 15:46

2 Answers 2

2

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;
}
// ...
Sign up to request clarification or add additional context in comments.

2 Comments

Hey thanks .. just a doubt ...what exactly is happening in the first line of the code ? const grouped ....
It creates a Map from an array of key/value pairs. So the data is mapped to such key/value pairs and then provided to the Map constructor. Maps can only have unique keys, so it will create 2 Map entries for the example. Those two objects start out with only the label property ({ label } does that).
1

Lodash

_.groupBy() by the label, _.map() the groups, and merge each group using _.mergeWith(), and _.omit() the id. When merging the groups, if the current value is a number, sum the current and new values, if not return undefined - If customizer returns undefined, merging is handled by the method instead.

const arr = [{"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"}]

const result = _(arr)
  .groupBy('label')
  .map((g) => _.omit(_.mergeWith({}, ...g, (objValue, srcValue) => _.isNumber(objValue) ? objValue + srcValue : undefined), 'id'))
  .value()
  
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>

ES6

Iterate the array with Array.reduce(). On each iteration check if the accumulator (the Map) has the label, and if not add an empty object with the label as the key. Iterate the current object keys with Array.forEach(), ignore id, and sum the numeric values. To get an array spread the Map.values():

const arr = [{"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"}]

const result = [...arr.reduce((m, o) => {
  m.has(o.label) || m.set(o.label, {})
  
  const obj = m.get(o.label)
  
  Object.keys(o).forEach((k) => {
    if(k === 'id') return
    
    obj[k] = typeof o[k] === 'number' ? (obj[k] || 0) + o[k] : o[k]
  })
  
  return m
}, new Map()).values()]
  
console.log(result)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.