29

I have below array of objects,

var data = [
    {
        label: "Book1",
        data: "US edition"
    },
    {
        label: "Book1",
        data: "UK edition"
    },
    {
        label: "Book2",
        data: "CAN edition"
    }
];

I want to merge the duplicate objects based on attribute 'label' so that Final output will look like below,

var data = [
    {
        label: "Book1",
        data: ["US edition", "UK edition"] //data attribute is merged
    },
    {
        label: "Book2",
        data: "CAN edition"
    }
];

Can someone help me identify the approach?

2
  • stackoverflow.com/questions/171251/… Commented May 4, 2015 at 8:53
  • In my case, new Set(array1, array2) works perfect. Commented Sep 22, 2019 at 11:58

5 Answers 5

33

I would probably loop through with filter, keeping track of a map of objects I'd seen before, along these lines (edited to reflect your agreeing that yes, it makes sense to make (entry).data always an array):

var seen = {};
data = data.filter(function(entry) {
    var previous;

    // Have we seen this label before?
    if (seen.hasOwnProperty(entry.label)) {
        // Yes, grab it and add this data to it
        previous = seen[entry.label];
        previous.data.push(entry.data);

        // Don't keep this entry, we've merged it into the previous one
        return false;
    }

    // entry.data probably isn't an array; make it one for consistency
    if (!Array.isArray(entry.data)) {
        entry.data = [entry.data];
    }

    // Remember that we've seen it
    seen[entry.label] = entry;

    // Keep this one, we'll merge any others that match into it
    return true;
});

In an ES6 environment, I'd use seen = new Map() rather than seen = {}.

Note: Array.isArray was defined by ES5, so some quite older browsers like IE8 won't have it. It can easily be shimmed/polyfilled, though:

if (!Array.isArray) {
    Array.isArray = (function() {
        var toString = Object.prototype.toString;
        return function(a) {
            return toString.call(a) === "[object Array]";
        };
    })();
}

Side note: I'd probably also always make entry.data an array, even if I didn't see two values for it, because consistent data structures are easier to deal with. I didn't do that above because your end result showed data being just a string when there was only one matching entry. (We've done that above now.)

Live example (ES5 version):

var data = [
    {
        label: "Book1",
        data: "US edition"
    },
    {
        label: "Book1",
        data: "UK edition"
    },
    {
        label: "Book2",
        data: "CAN edition"
    }
];
snippet.log("Before:");
snippet.log(JSON.stringify(data, null, 2), "pre");
var seen = {};
data = data.filter(function(entry) {
    var previous;

    // Have we seen this label before?
    if (seen.hasOwnProperty(entry.label)) {
        // Yes, grab it and add this data to it
        previous = seen[entry.label];
        previous.data.push(entry.data);

        // Don't keep this entry, we've merged it into the previous one
        return false;
    }

    // entry.data probably isn't an array; make it one for consistency
    if (!Array.isArray(entry.data)) {
        entry.data = [entry.data];
    }

    // Remember that we've seen it
    seen[entry.label] = entry;

    // Keep this one, we'll merge any others that match into it
    return true;
});
snippet.log("After:");
snippet.log(JSON.stringify(data, null, 2), "pre");
<!-- Script provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="http://tjcrowder.github.io/simple-snippets-console/snippet.js"></script>

Sign up to request clarification or add additional context in comments.

10 Comments

What an optimized and superb logic :)
How does data get the updated array for the duplicated ones?
@FinbarMaginn: I don't understand the question.
I don't see how the merged array is assigned to the returned array item with the same label. Also I was wondering if this whole thing could be rewritten with a Map and Reduce?
@FinbarMaginn: entry.data = [entry.data]; and previous = seen[entry.label]; previous.data.push(entry.data); are what do that. Re map/reduce: Possibly. There's no need for the complexity over the simple loop above, though.
|
8

Try with below approach, it works find and it is short

 var data = [
    {
        label: "Book1",
        data: "US edition"
    },
    {
        label: "Book1",
        data: "UK edition"
    },
    {
        label: "Book2",
        data: "CAN edition"
    }
];

const result = Array.from(new Set(data.map(s => s.label)))
    .map(lab => {
      return {
        label: lab,
        data: data.filter(s => s.label === lab).map(edition => edition.data)
      }
    })

console.log(result);

1 Comment

This works great but a little note, if you have more keys for the object you will need to find a way to add them, my solution for this ...data.filter(f => f.label === lab)[0],
3

I came across the same situation and I was hoping to use the Set instead of mine here

const data = [
  {
    label: "Book1",
    data: "US edition"
  },
  {
    label: "Book1",
    data: "UK edition"
  },
  {
    label: "Book2",
    data: "CAN edition"
  },
  {
    label: "Book3",
    data: "CAN edition"
  },
  {
    label: "Book3",
    data: "CANII edition"
  }
];

const filteredArr = data.reduce((acc, current) => {
  const x = acc.find(item => item.label === current.label);
  if (!x) {
    const newCurr = {
      label: current.label,
      data: [current.data]
    }
    return acc.concat([newCurr]);
  } else {
    const currData = x.data.filter(d => d === current.data);
    if (!currData.length) {
      const newData = x.data.push(current.data);
      const newCurr = {
        label: current.label,
        data: newData
      }
      return acc;
    } else {
      return acc;
    }
    
  }
}, []);

console.log(filteredArr);

Comments

1

This code is tested on latest version of firefox. To work on other browsers change Array.isArray for a library as lodash or whatever you prefer.

var data = [
    {
        label: "Book1",
        data: "US edition"
    },
    {
        label: "Book1",
        data: "UK edition"
    },
    {
        label: "Book2",
        data: "CAN edition"
    }
],
i = 0,
j = data.length - 1,
current;

for (;i < data.length; i++) {
  current = data[i];
  for (;j > i; j--) {
     if (current.label === data[j].label) {
       if (Array.isArray(current.data)) {
         current.data = current.data.concat([data[j].data]);
       } else {
         current.data = [].concat([data[j].data, current.data]);
       }
       data.splice(j, 1);
     }

  }
} 

console.log(data);

Comments

0

var data = [
    {
        label: "Book1",
        data: "US edition"
    },
    {
        label: "Book1",
        data: "UK edition"
    },
    {
        label: "Book2",
        data: "CAN edition"
    }
].filter(function(e, index ,b) {
    k = b.map(z => z.label)
    if(k.includes(e.label, index+1)){
        indexPosition = k.indexOf(e.label, index+1);
        b[indexPosition].data = (e.data+"||"+b[indexPosition].data);
        
    }else{
        e.data = e.data.split("||").length > 1 ? e.data.split("||") : e.data;
        return e;
    }
});

console.log(data)

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.