Reputation: 1620
I have some objects in the shape of below.
[{
product: 'ABC',
productId: 'AB123',
batch: 'BA1',
price: '12'
}, {
product: 'ABC',
productId: 'AB123',
batch: 'BA2',
price: '15'
}, {
product: 'XYZ',
productId: 'AB124',
batch: 'XY1',
price: '124'
}]
I want to merge objects into one single object in the array if key pair (product
, and productId
) are mathced, in the below format.
[{
product: 'ABC',
productId: 'AB123',
batch: ['BA1', 'BA2'],
price: ['12', '15']
}, {
product: 'XYZ',
productId: 'AB124',
batch: 'XY1',
price: '124'
}]
How can I do it in lodash or in pure javascript.
Upvotes: 0
Views: 2786
Reputation: 191976
You can merge similar objects in the array using a lodash's chain with _.transform()
and _.mergeWith()
:
function mergeSimilar(arr, arrayProps) {
// transform the array into a map object
return _(arr).transform(function(result, item) {
// create a temp id that includes the product and productId
var id = item.product + item.productId;
// merge the existing item with a new item
result[id] = _.mergeWith(result[id] || {}, item, function(objValue, srcValue, key) {
// if a value exists, and it's one of the request keys, concat them into a new array
if (!_.isUndefined(objValue) && _.includes(arrayProps, key)) {
return [].concat(objValue, srcValue);
}
});
}, {})
.values() // get the values from the map object
.value();
}
var arr = [{
product: 'ABC',
productId: 'AB123',
batch: 'BA1',
price: '12'
}, {
product: 'ABC',
productId: 'AB123',
batch: 'BA2',
price: '15'
}, {
product: 'XYZ',
productId: 'AB124',
batch: 'XY1',
price: '124'
}];
var result = mergeSimilar(arr, ['batch', 'price']);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.2/lodash.min.js"></script>
Upvotes: 1
Reputation: 7416
Lodash 4.17.2
_.reduce(data, function(result, item) {
var added = _.find(result, {
product: item.product,
productId: item.productId
});
if (_.isObject(added)) {
//!! better to merge with new object and add result to array again to avoid mutable
added = _.mergeWith(added, item, function(addedVal, itemVal, key) {
if (key === 'product' || key === 'productId') {
return addedVal;
}
return _.concat(addedVal, itemVal);
});
return result;
}
return _.concat(result, item);
}, []);
Upvotes: 1
Reputation: 6922
You can make use of _.uniqWith to loop over the collection and get rid of duplicates. Apart from that, uniqWith grants you access to the objects themselves so you can tamper them as you like.
In this case, when a duplicate is found, I add its batch and price to the array of the original object, getting the desired result.
var arr = [{
product: 'ABC',
productId: 'AB123',
batch: 'BA1',
price: '12'
}, {
product: 'ABC',
productId: 'AB123',
batch: 'BA2',
price: '15'
}, {
product: 'XYZ',
productId: 'AB124',
batch: 'XY1',
price: '124'
}];
function addToArray(val1, val2) {
return _.isArray(val1) ? val1.concat(val2) : [val1].concat(val2);
}
function modifyObjs(a, b) {
b.batch = addToArray(b.batch, a.batch);
b.price = addToArray(b.price, a.price);
return true;
}
function predicateAndModifier(a, b) {
return a.product === b.product && a.productId === b.productId && modifyObjs(a, b);
}
console.log(_.uniqWith(arr, predicateAndModifier));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.2/lodash.min.js"></script>
Upvotes: 1
Reputation: 7385
Does it have to be readable?
var data = [{
product: 'ABC',
productId: 'AB123',
batch: 'BA1',
price: '12'
}, {
product: 'ABC',
productId: 'AB123',
batch: 'BA2',
price: '15'
}, {
product: 'ABC',
productId: 'AB113',
batch: 'BA2',
price: 15
}, {
product: 'XYZ',
productId: 'AB124',
batch: 'XY1',
price: '124'
}]
var unEs6 = function(x) {
if (x instanceof Map) {
var result = {}
for (let [key, value] of x.entries()) {
result[key] = unEs6(value);
}
return result;
}
else {
return x
}
}
JSON.stringify(unEs6(
data
.map(
row => (new Map().set(
row.product, new Map().set(
row.productId, new Map()
.set("batch", [row.batch])
.set("price", [row.price])
)
)
)
)
.reduce((a, b) => !a.has(b.keys().next().value) ?
new Map([...a, ...b]) :
!a.get(b.keys().next().value).has(b.get(b.keys().next().value).keys().next().value) ?
a.set(b.keys().next().value, new Map([
...a.get(b.keys().next().value),
...b.get(b.keys().next().value)
])) :
a.set(b.keys().next().value, a.get(b.keys().next().value).set(
b.get(b.keys().next().value).keys().next().value,
new Map()
.set("batch", a.get(b.keys().next().value).get(b.get(b.keys().next().value).keys().next().value).get("batch")
.concat(b.get(b.keys().next().value).get(b.get(b.keys().next().value).keys().next().value).get("batch"))
)
.set("price", a.get(b.keys().next().value).get(b.get(b.keys().next().value).keys().next().value).get("price")
.concat(b.get(b.keys().next().value).get(b.get(b.keys().next().value).keys().next().value).get("price"))
)
))
)
))
Upvotes: 0
Reputation: 386620
This proposal does not alter the given data.
It creates new objects, first with just single data, later if more data should be grouped, it uses an array for batch
and price
.
var data = [{ product: 'ABC', productId: 'AB123', batch: 'BA1', price: '12' }, { product: 'ABC', productId: 'AB123', batch: 'BA2', price: '15' }, { product: 'XYZ', productId: 'AB124', batch: 'XY1', price: '124'}],
merged = [];
data.forEach(function (a) {
if (!this[a.productId]) {
this[a.productId] = { product: a.product, productId: a.productId, batch: a.batch, price: a.price };
merged.push(this[a.productId]);
return;
}
if (!Array.isArray(this[a.productId].batch)) {
this[a.productId].batch = [this[a.productId].batch];
}
if (!Array.isArray(this[a.productId].price)) {
this[a.productId].price = [this[a.productId].price];
}
this[a.productId].batch.push(a.batch);
this[a.productId].price.push(a.price);
}, Object.create(null));
console.log(merged);
Upvotes: 3