I have an array of objects like this:
[
{
item: 'abc',
category: 'car',
price: 1000
},
{
item: 'abc',
category: 'bike',
price: 1000
},
{
item: 'abc',
category: 'car',
price: 2000
},
{
item: 'abc',
category: 'car',
price: 3000
},
{
item: 'abc',
category: 'bike',
price: 500
},
{
item: 'abc',
category: 'plane',
price: 9000
},
]
I want to create a new Array from above array which groups objects by category and has total price for that category as value for 'price' key:
[
{
category: car,
price: 6000
},
{
category: bike,
price: 1500
},
{
category: plane,
price: 9000
}
]
How can I do this in Javascript?
Basic approach is
create a map first with category as key and price as value
convert this map into an array
DEMO
var input = [
{
item: 'abc',
category: 'car',
price: 1000
},
{
item: 'abc',
category: 'bike',
price: 1000
},
{
item: 'abc',
category: 'car',
price: 2000
},
{
item: 'abc',
category: 'car',
price: 3000
},
{
item: 'abc',
category: 'bike',
price: 500
},
{
item: 'abc',
category: 'plane',
price: 9000
},
];
var map = {};
input.forEach( function ( item ){
map [ item.category ] = map [ item.category ] || 0;
map [ item.category ] += item.price;
});
var output = Object.keys( map ).map( function(key){ return { category: key, price : map[key] } });
console.log( output );
You could group it with a hash table and take it as reference for adding the values.
Main structure:
grouped = data.reduce(function (hash) { // closure over hash
return function (r, a) { /* ... */ }; // returns function for callback
}(Object.create(null)), []); // call of IFFE with an empty object
The main idea is to use a variable hash outside of the global context, but usable inside of the callback for Array#reduce.
This works with an IIFE (Immediately Invoked Function Expression), which is a function which is directly after declaration invoked with an empty object.
This creates the variable hash with an empty object and returns the function for the callback.
Inside of the callback:
The callback works with two variables, r and a.
function (r, a) {
// business logic
return r;
}
At start, variable r gets the initialValue, of an empty array []. In this array, the result set is collected and returned for every iteration. This parameter is describes as accumulator.
Parameter a is the current value of the array. This is a single object, like
{
item: 'abc',
category: 'car',
price: 1000
}
and only one object is being processed at the same time.
Basically this solution works with an object and it looks at the end like this
{
car: {
category: "car",
price: 6000
},
bike: {
category: "bike",
price: 1500
},
plane: {
category: "plane",
price: 9000
}
}
where the properties, like car or bike give the reference to the result array without the property car or bike.
At first, a check is made, if the category exists in the hash table. If not, then a new object is created with category and a price with zero, because the price property gets updated for every item within the same category. Then the new object reference gets pushed to the result array.
After the check, the property exist already or is just created, the price property gets an update with the actual value.
At last, the result set is returned for the next update iteration or as final result.
var data = [{ item: 'abc', category: 'car', price: '1000' }, { item: 'abc', category: 'bike', price: '1000' }, { item: 'abc', category: 'car', price: '2000' }, { item: 'abc', category: 'car', price: '3000' }, { item: 'abc', category: 'bike', price: '500' }, { item: 'abc', category: 'plane', price: '9000' }],
grouped = data.reduce(function (hash) {
return function (r, a) {
if (!hash[a.category]) {
hash[a.category] = { category: a.category, price: 0 };
r.push(hash[a.category]);
}
hash[a.category].price += +a.price;
return r;
};
}(Object.create(null)), []);
console.log(grouped);
.as-console-wrapper { max-height: 100% !important; top: 0; }
var data = [{item: 'abc',category: 'car',price: 1000},{item:'abc',category: 'bike',price: 1000},{item: 'abc',category: 'car',price: 2000},{item: 'abc',category: 'car',price: 3000},{item: 'abc',category: 'bike',price: 500},{item: 'abc',category: 'plane',price: 9000}];
var groupArr = [];
var res = data.reduce(function(all, item, index){
var i = groupArr.findIndex(x => x.category == item.category);
if(i <= -1)
groupArr.push({category: item.category, price: item.price});
else
groupArr[i].price += item.price;
return all;
},{})
console.log(groupArr)
See this if this helps
var obj = [{
item: 'abc',
category: 'car',
price: 1000
}, {
item: 'abc',
category: 'bike',
price: 1000
}, {
item: 'abc',
category: 'car',
price: 2000
}, {
item: 'abc',
category: 'car',
price: 3000
}, {
item: 'abc',
category: 'bike',
price: 500
}, {
item: 'abc',
category: 'plane',
price: 9000
}]
var newobj = {};
obj.forEach(function(record) {
newobj[record.category] = {
category: record.category,
price : newobj[record.category] && newobj[record.category].price ? newobj[record.category].price + record.price : record.price
}
})
var result = [];
Object.keys(newobj).forEach(function(key) {
result.push(newobj[key])
})
console.log(result);
Related
Basically I need to do an 'all except' option for when you are filtering table of products.
I have two arrays: uniqueProducts and selectedFilters.
uniqueProducts contains objects of products that are to be displayed in a table.
selectedFilters contains objects of the pre-selected filters.
Here's what an object of the uniqueProducts array looks like:
{category: 'radio', price: '50', manufacturer: 'sony', production_date: '2020/05/30'}
Here's what the objects of the selectedFilters array look like:
{type: 'manufacturer', value: 'sony', status: true}
{type: 'category', value: 'radio', status: true}
Because the price filter has min and max, I will filter through prices once I have the array of objects ready.
Filtering also does not include production_date.
So, the selectedFilters objects's type property can be either of the two above.
What I need to do is to filter through the uniqueProducts, and only get the objects that do not match any of the type and value of the objects in selectedFilters.
To put it differently, when you select filters for the products, and click 'all except', you have to see all the products that do not match that filter. Hope you get what I mean to say.
I've tried doing something like this:
uniqueProducts.forEach((product)=>{
for(let i = 0; i < selectedFilters.length; i++) {
if(product[selectedFilters[i].type] !== selectedFilters[i].value) {
allExceptArray.push(product)
}
}
})
I've tried nesting for..loops too, and various other conditions, but the issue I keep running into is that, every time a particular property of an object DOES NOT match, it gets pushed into the array, even if the other property of that same object DOES match.
Can somebody help me figure out how to do the logic here?
The following approach is based on a single reduce task where the reducer function incorporates a some based filter/comparison task.
The filter/match process is implemented in a way that a match is assumed as soon as the criteria of just one filter-configuration matches a single item property.
function isMatchingItem({ type: key, value }, item) {
return item[key] === value;
}
function collectNonMatchingItem({ filters = [], result = [] }, item) {
if (
!filters.some(filter => isMatchingItem(filter, item))
) {
result.push(item);
}
return { filters, result };
}
const uniqueProducts = [
{ category: 'radio', price: '50', manufacturer: 'sony', production_date: '2020/05/30' },
{ category: 'radio', price: '70', manufacturer: 'philips', production_date: '2020/05/30' },
{ category: 'tv', price: '500', manufacturer: 'sony', production_date: '2020/05/30' },
{ category: 'tv', price: '650', manufacturer: 'philips', production_date: '2020/05/30' },
{ category: 'console', price: '900', manufacturer: 'sony', production_date: '2020/05/30' },
{ category: 'console', price: '700', manufacturer: 'nintendo', production_date: '2020/05/30' },
];
const selectedFilters = [
{ type: 'manufacturer', value: 'sony', status: true },
{ type: 'category', value: 'radio', status: true },
];
const result = uniqueProducts.reduce(collectNonMatchingItem, {
filters: selectedFilters,
result: [],
}).result;
console.log({ result });
.as-console-wrapper { min-height: 100%!important; top: 0; }
Edit
"... So, I need to now do the case when prices have been selected too and need to be all except filtered. I know I have to filter through the result array, but again, how do I make sure that only the objs that DO NOT match the min/max price are taken? ... My first instinct now is to just reverse the condition, i.e. do if NOT match, but then, what do I put inside? It doesn't feel proper to leave the if block empty. How should I go about doing it?" – user17144382
The final price filtering of cause should be based on filter. Since the OP wants to filter every item where the item's price value is not within a certain min/max price range, one just needs to adapt the price value comparison accordingly. More important, filter like most of the Array methods supports a second argument, the thisArg, which gets applied as context of any filter function which supports this binding.
Thus it's not that difficult to come up with the additional implementation and usage of a properly named filter function ...
function isOutOfBoundPriceRange(item) {
const { minPrice, maxPrice } = this;
const price = parseFloat(item.price);
return (price < parseFloat(minPrice)) || (price > parseFloat(maxPrice));
}
function isMatchingItem({ type: key, value }, item) {
return item[key] === value;
}
function collectNonMatchingItem({ filters = [], result = [] }, item) {
if (
!filters.some(filter => isMatchingItem(filter, item))
) {
result.push(item);
}
return { filters, result };
}
const uniqueProducts = [
{ category: 'cellphone', price: '19', manufacturer: 'nokia', production_date: '2020/05/30' },
{ category: 'radio', price: '50', manufacturer: 'sony', production_date: '2020/05/30' },
{ category: 'radio', price: '70', manufacturer: 'philips', production_date: '2020/05/30' },
{ category: 'tv', price: '500', manufacturer: 'sony', production_date: '2020/05/30' },
{ category: 'tv', price: '650', manufacturer: 'philips', production_date: '2020/05/30' },
{ category: 'console', price: '900', manufacturer: 'sony', production_date: '2020/05/30' },
{ category: 'console', price: '700', manufacturer: 'nintendo', production_date: '2020/05/30' },
];
const selectedFilters = [
{ type: 'manufacturer', value: 'sony', status: true },
{ type: 'category', value: 'radio', status: true },
];
const priceFilter = {
minPrice: 20,
maxPrice: 650,
};
const result = uniqueProducts.reduce(collectNonMatchingItem, {
filters: selectedFilters,
result: [],
}).result.filter(isOutOfBoundPriceRange, priceFilter);
console.log({ priceFilter, result });
.as-console-wrapper { min-height: 100%!important; top: 0; }
I have two arrays. look by id, find FIRST occurrence of element from Array1 IN Array2, Take the Index that element is found at in Array2, and make a new Array where element in Array1 is moved to the new index found in Array2. in Array1 {id: 001} is at index 0, I would like it to be in a new Array at index 1 (index it is FIRST found in Array2). {id: 002} is at index 1 in Array1, I would like this to be in the new Array at index 3( index it is FIRST found in Array2) so on....
const Array1 = [
{
id: '001',
school: "blue springs"
},
{
id: '002',
school: "sycamore hills"
},
{
id: '003',
school: "moreland ridge"
},
{
id: '004',
school: "grain valley"
}
]
const Array2 = [
{
id: '003',
participant: "Susan"
},
{
id: '001',
participant: "Henry"
},
{
id: '003',
participant: "Justin" <---- if 003 exists do not duplicate the return array, this is my issue....
},
{
id: '004',
participant: "Jessica"
},
{
id: '002',
participant: "Carly"
},
{
id: '001',
participant: "Chloe" <---- if 001 exists do not duplicate the return array, this is my issue....
}
// got this far,
const finder = Array1.map((el) => { return Array2.findIndex((el2) => { return el2.id === el.id}) })
console.log(finder)
// [ 1, 4, 0, 3 ] <--- need to move Array1 objects to these new indexes
expected output
const Result = [
{
id: '003',
school: "moreland ridge"
},
{
id: '001',
school: "blue springs"
},
{
id: '004',
school: "grain valley"
},
{
id: '002',
school: "sycamore hills"
}
]
You first filter to remove duplicates on Array2, and then look for a match in Array1 regarding id's
const Array1 = [
{
id: '001',
school: "blue springs"
},
{
id: '002',
school: "sycamore hills"
},
{
id: '003',
school: "moreland ridge"
},
{
id: '004',
school: "grain valley"
}
]
const Array2 = [
{
id: '003',
participant: "Susan"
},
{
id: '001',
participant: "Henry"
},
{
id: '003',
participant: "Justin" // <---- if 003 exists do not duplicate the return array, this is my issue....
},
{
id: '004',
participant: "Jessica"
},
{
id: '002',
participant: "Carly"
},
{
id: '001',
participant: "Chloe" // <---- if 001 exists do not duplicate the return array, this is my issue....
}
]
const alreadyShown = {};
const res = Array2.filter( el => {
if (!alreadyShown[el.id]){
alreadyShown[el.id] = true;
return true;
}
return false;
}).map( x => Array1.find( y => x.id === y.id ) || x);
console.log(res)
NOTE: I included a logical OR to provide a fallback case, but this is not defined in OP request
I think you just need to sort the first array by the uniqueness of the identifier in the second array
const Array1 = [{id: '001',school: "blue springs"},{id: '002',school: "sycamore hills"},{id: '003',school: "moreland ridge"},{id: '004',school: "grain valley"}];
const Array2 = [{id: '003',participant: "Susan"},{id: '001',participant: "Henry"},{id: '003',participant: "Justin"},{id: '004',participant: "Jessica"},{id: '002',participant: "Carly"},{id: '001',participant: "Chloe" }];
const idOrder = Array2.map(({ id }) => id).filter((v, i, a) => a.indexOf(v)===i);
console.log('Order:', idOrder );
const sortedByOrder = Array1.sort(({ id: id1 }, { id: id2 }) =>
idOrder.indexOf(id1) - idOrder.indexOf(id2));
console.log('Result:', sortedByOrder);
.as-console-wrapper{min-height: 100%!important; top: 0}
I have the following code with the following arrays. I want to loop through both of them and pull out some data, and put them inside a final array. I am able to do that, but the contents are duplicated. I tried reading about reduce but don't quite understand it, and am not sure if it's the right solution. I have also setup a jsfiddle
https://jsfiddle.net/anders_kitson/Lcqn6fgd/
var lineItems = [{
id: 'li_1HyhAZHk5l44uIELgsMWqHqB',
object: 'item',
amount_subtotal: 7500,
amount_total: 7500,
currency: 'cad',
description: 'The Spencer',
price: [Object],
quantity: 1
},
{
id: 'li_1HyhAZHk5l44uIELeNUsiZPu',
object: 'item',
amount_subtotal: 7500,
amount_total: 7500,
currency: 'cad',
description: 'The Gertie',
price: [Object],
quantity: 1
}
]
var arr = [{
id: 'prod_IS1wY1JvSv2CJg',
object: 'product',
active: true,
attributes: [],
created: 1606248785,
description: 'Shelf Set',
images: [
'https://files.stripe.com/links/fl_test_raNEqk9ZhzX3WdQsnvXX4gFq'
],
livemode: false,
metadata: {},
name: 'The Spencer',
statement_descriptor: null,
type: 'service',
unit_label: null,
updated: 1606248785
},
{
id: 'prod_IS299dMnC13Ezo',
object: 'product',
active: true,
attributes: [],
created: 1606249543,
description: 'Shelf Set',
images: [
'https://files.stripe.com/links/fl_test_QPbrP76uNn4QadgcUwUnkmbe'
],
livemode: false,
metadata: {},
name: 'The Gertie',
statement_descriptor: null,
type: 'service',
unit_label: null,
updated: 1606249543
}
];
let productArr = [];
arr.map((item) => {
lineItems.map((line) => {
productArr.push({
image: item.images[0],
name: item.name,
price: line.amount_total,
});
});
});
console.log(productArr);
This is the output I get where you can see the array repeats the values, and I know I have coded it this way I just don't know how to fix it.
[{
image: "https://files.stripe.com/links/fl_test_raNEqk9ZhzX3WdQsnvXX4gFq",
name: "The Spencer",
price: 7500
}, {
image: "https://files.stripe.com/links/fl_test_raNEqk9ZhzX3WdQsnvXX4gFq",
name: "The Spencer",
price: 7500
}, {
image: "https://files.stripe.com/links/fl_test_QPbrP76uNn4QadgcUwUnkmbe",
name: "The Gertie",
price: 7500
}, {
image: "https://files.stripe.com/links/fl_test_QPbrP76uNn4QadgcUwUnkmbe",
name: "The Gertie",
price: 7500
}]
To Be more clear this is the output that I want
[{
image: "https://files.stripe.com/links/fl_test_raNEqk9ZhzX3WdQsnvXX4gFq",
name: "The Spencer",
price: 7500
}, {
image: "https://files.stripe.com/links/fl_test_QPbrP76uNn4QadgcUwUnkmbe",
name: "The Gertie",
price: 7500
},
]
I have tried the suggestion in the comments with the following
let b
arr.map((item) => {
b = lineItems.map((line) => {
return {
image: item.images[0],
name: item.name,
price: line.amount_total,
};
});
});
but it returns the same ones twice
[{
image: "https://files.stripe.com/links/fl_test_QPbrP76uNn4QadgcUwUnkmbe",
name: "The Gertie",
price: 7500
}, {
image: "https://files.stripe.com/links/fl_test_QPbrP76uNn4QadgcUwUnkmbe",
name: "The Gertie",
price: 7500
}]
Although not expressed directly in your question, it seems you're looking to do a join in javascript. The only things I see relating the two are 'name' in products and 'description' in the line items. So do a loop join on that.
Here's some sample code using your example but stripped down only to what's relevant:
var lineItems = [
{ amount_total: 7500, description: 'The Spencer' },
{ amount_total: 7500, description: 'The Gertie' }
]
var arr = [
{ images: ['Spencer Image 1'], name: 'The Spencer' },
{ images: ['Gertie Image 1'], name: 'The Gertie' }
]
let joined = arr
.flatMap(a => lineItems.map(li => ({a, li})))
.filter(obj => obj.a.name == obj.li.description)
.map(obj => ({
image: obj.a.images[0],
name: obj.a.name,
price: obj.li.amount_total
}));
console.log(joined);
Being a loop join, it may not be that efficient. To do a hash join is a little more involved. You can look through the source code of my developing project
fluent-data, or it might even be useful to you to use it directly if you can follow the documentation.
You can use a single map call and reference your second lineItems array either by index, if you know that the two arrays are the same length and order
const output = arr.map((o, i) => ({
name: o.name,
image: o.images[0],
price: lineItems[i].amount_total}
));
or by using find() to retrieve the relevant object.
const outputUsingFind = arr.map(o => {
const lineItem = lineItems.find(item => item.description === o.name);
// ** add lineItem valid check here **
return {
name: o.name,
image: o.images[0],
price: lineItem.amount_total};
});
var lineItems = [{amount_subtotal: 7500,amount_total: 700,description: 'The Spencer',},{amount_subtotal: 7500,amount_total: 500,description: 'The Gertie',}];
var arr = [{images: ['spencer image'],name: 'The Spencer',},{images: ['gertie image'],name: 'The Gertie'}];
// since your arrays are ordered the same you can access the second object using
// the index passed from map.
const output = arr.map((o, i) => ({
name: o.name,
image: o.images[0],
price: lineItems[i].amount_total}
));
console.log(output);
// if the two arrays are not in the same order you can use find() to retrieve
// the second object by property (you'll need to check
const outputUsingFind = arr.map(o => {
const lineItem = lineItems.find(item => item.description === o.name);
// ** add lineItem valid check here **
return {
name: o.name,
image: o.images[0],
price: lineItem.amount_total};
});
console.log(outputUsingFind);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I have a nested array of objects and I want to groupBy id and form a new array. Here's my array:
mainArray = [
{ name: 'a',age: 10, company: [ { desc: 'test1' , id: 6 }, { desc: 'testa' , id: 10 }] },
{ name: 'b',age: 20, company: [ { desc: 'test2' , id: 30 }] },
{ name: 'c',age: 40, company: [ { desc: 'test3' , id: 10 }, { desc: 'testc' , id: 30 }] }
]
I can flatten the entire array but it doesn't seem like the right way to do it.
My new array should look like something like this:
result = [
comapny_6: [
{
name: 'a',
age: 10,
desc: 'test1'
},
],
comapny_10: [
{
name: 'a',
age: 10,
desc: 'testa'
},
{
name: 'c',
age: 40,
desc: 'test3'
}
],
company_30 :[
{
name: 'b',
age: 20,
desc: 'test2'
},
{
name: 'c',
age: 40,
desc: 'testc'
}
]
]
I am open to suggestions on how the final data structure should look like. The bottom line is I want groupBy id so that I have information about each company separated out.
You can use reduce to loop thru the array and construct the desired object output. Use forEach to loop thru company
var mainArray = [{"name":"a","age":10,"company":[{"desc":"test1","id":6},{"desc":"testa","id":10}]},{"name":"b","age":20,"company":[{"desc":"test2","id":30}]},{"name":"c","age":40,"company":[{"desc":"test3","id":10},{"desc":"testc","id":30}]}];
var result = mainArray.reduce((c, {name,age,company}) => {
company.forEach(({id,desc}) => (c["company_" + id] = c["company_" + id] || []).push({name,age,desc}));
return c;
}, {});
console.log(result);
You can first create a 1D array using flatMap() and then use reduce() to group
const mainArray = [
{ name: 'a',age: 10, company: [ { desc: 'test1' , id: 6 }, { desc: 'testa' , id: 10 }] },
{ name: 'b',age: 20, company: [ { desc: 'test2' , id: 30 }] },
{ name: 'c',age: 40, company: [ { desc: 'test3' , id: 10 }, { desc: 'testc' , id: 30 }] }
]
const flat = mainArray.flatMap(({company,...rest}) => company.map(a => ({...rest,...a})));
const res = flat.reduce((ac,{id,...rest}) => ((ac[`company_${id}`] || (ac[`company_${id}`] = [])).push(rest),ac),{})
console.log(res)
Explanation
reduce() is method with returns a single value after iterating through all the array. The accumulator i.e ac in above case is set to empty object {}(which is the second argument passed to function)
In each iteration we return the updated accumulator which becomes ac for next iteration. So what we return from function is following expression
((ac[`company_${id}`] || (ac[`company_${id}`] = [])).push(rest),ac)
ac[company_${id}] is using Bracket Notation which takes an expression company_${id}. It is same as
ac["company_" + id]
The above line checks if ac[company_${id}] exists in the ac then push() rest to the it.
If ac[company_${id}] is not created yet they set it to empty array [] then push() the rest to it.
The last part uses comma operator
((ac[`company_${id}`] || (ac[`company_${id}`] = [])).push(rest),ac)
The above whole expression will evaluate to the last value separated by comma , which is ac. So in each iteration we are pushing rest to the respective array and returning ac it the end. The code is equivalent to
const res = flat.reduce((ac,{id,...rest}) => {
//check if company id doesnot exist as key in ac then set it empty array
if(!ac[`company_${id}`]) ac[`company_${id}`] = [];
//push rest(which will be an object with all the keys expect id)
ac[`company_${id}`].push(rest)
//at last return ac
return ac;
})
You can achieve this with Array.reduce and inside it with an Array.forEach over the array of companies like this:
let data = [ { name: 'a',age: 10, company: [ { desc: 'test1' , id: 6 }, { desc: 'testa' , id: 10 }] }, { name: 'b',age: 20, company: [ { desc: 'test2' , id: 30 }] }, { name: 'c',age: 40, company: [ { desc: 'test3' , id: 10 }, { desc: 'testc' , id: 30 }] } ]
let result = data.reduce((r,{ name, age, company }) => {
company.forEach(({ id, desc }) =>
r[`company_${id}`] = (r[`company_${id}`] || []).concat({ name, age, desc }))
return r
}, {})
console.log(result)
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.
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);
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>
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);
}, []);
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>
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"))
)
))
)
))