I have below array of objects,
var data = [
{
label: \"Book1\",
data: \"US edition\"
},
{
label: \"Book1\",
data:
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);
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 (We've done that above now.)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.
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>
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);