Reputation: 2196
I have an array of product objects, each product contains the product price besides a nested object representing the currency used.
var products = [
{
'id': '1',
'currency': { id:1, name:'Dollar' },
'price': '100'
}, {
'id': '4',
'currency': {
id:2,
name:'Euro',
current_quotation: {
date: '2020-03-02',
quotation: 68
}
},
'price': '300'
}, {
'id': '6',
'currency': { id:1, name:'Dollar' },
'price': '50'
}, {
'id': '16',
'currency': null,
'price': '50'
}, {
'id': '23',
'currency': { id:2, name:'Euro' },
'price': null
}
];
What I'm trying to do is to get an array with a unique object per currency with its price sum, preferably using lodash or ES6 script. What I need as a result is:
var subtotals = [
{currency:'Dollar', total: 150},
{currency:'Euro', total: 300}
]
I tried with many samples but no luck so far. My approach is this, but it's returning the whole product object within the array, which is so far from expected result
let subtotals = state.products.reduce((h, product) => Object.assign(h, { [product.currency?.name]:( h[product.currency?.name] || [] ).concat({currency: product.currency?.name, price: product.price}) }), {})
Result
{
"Euro":[
{"currency":"Euro","price":"300"}
],
"Dolar":[
{"currency":"Dolar","price":"100"},
{"currency":"Dolar","price":"50"}
]
}
Please note that some products may have no currency at the begining since it value comes from a dropdown input. In that case the value can be ommited in the result.
Any help will be appreciated.
Upvotes: 0
Views: 710
Reputation: 50749
Using lodash, you can group by the currency object's name
property using _.groupBy()
. And then map the result object to an array of objects. The object which you map to can use the key as the currency and the summed total of the grouped array (using _.sumBy()
) as the total:
const products = [ { 'id': '1', 'currency': { id:1, name:'Dollar' }, 'price': '100' }, { 'id': '4', 'currency': { id:2, name:'Euro' }, 'price': '300' }, { 'id': '6', 'currency': { id:1, name:'Dollar' }, 'price': '50' }, { 'id': '16', 'currency': null, 'price': '50' }, { 'id': '23', 'currency': { id:2, name:'Euro' }, 'price': null } ];
const getSubTotals = p => _.flow(
products => _.filter(products, o => _.has(o, p)),
sm_prod => _.groupBy(sm_prod, p),
gr => _.map(gr, (arr, currency) => ({currency, total: _.sumBy(arr, ({price}) => +price)}))
);
console.log(getSubTotals('currency.name')(products));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
EDIT As you have changed your question. You'll need to use some alternate code to get your desired result:
const products = [ { 'id': '1', 'currency': { id:1, name:'Dollar' }, 'price': '100' }, { 'id': '4', 'currency': { id:2, name:'Euro', current_quotation: { date: '2020-03-02', quotation: 68 } }, 'price': '300' }, { 'id': '6', 'currency': { id:1, name:'Dollar' }, 'price': '50' }, { 'id': '16', 'currency': null, 'price': '50' }, { 'id': '23', 'currency': { id:2, name:'Euro' }, 'price': null } ];
const getSubTotals = p => products => {
const _gr = _(products).filter(o => _.has(o, p)).groupBy(p);
const quote_map = _gr.mapValues((arr, k) => _.sumBy(arr, o => _.get(o, 'currency.current_quotation.quotation', 0)) || 1).value();
const _totals = _gr.map((arr, currency) => ({currency, total: _.sumBy(arr, ({price}) => +price)}));
return _totals.map(({currency, total, ...r}) => ({currency, total, quote_total: total*quote_map[currency]})).value();
}
console.log(getSubTotals('currency.name')(products));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
Upvotes: 1
Reputation: 21
I wrote this up in the console and it should work for your case:
var products = [
{
id: '1',
currency: { id: 1, name: 'Dollar' },
price: '100',
},
{
id: '4',
currency: { id: 2, name: 'Euro' },
price: '300',
},
{
id: '6',
currency: { id: 1, name: 'Dollar' },
price: '50',
},
];
subtotals = {};
products.map(x => {
if(x.currency !== null){
subtotals[x.currency.name] === undefined
? (subtotals[x.currency.name] = parseInt(x.price))
: (subtotals[x.currency.name] += parseInt(x.price));
}
});
Upvotes: 0
Reputation: 370759
Assuming that you want to sum up the totals and get a Euro total of 300:
Iterate over each currency.name
and price
, transforming the input into an object whose keys are the currency names and values are the cumulative price for that currency name found so far. At the end, map the object's entries to get an array of objects of your desired format:
var products = [
{
'id': '1',
'currency': { id:1, name:'Dollar' },
'price': '100'
}, {
'id': '4',
'currency': { id:2, name:'Euro' },
'price': '300'
}, {
'id': '6',
'currency': { id:1, name:'Dollar' },
'price': '50'
}
];
const pricesByName = {};
for (const { currency: { name }, price } of products) {
pricesByName[name] = (pricesByName[name] || 0) + Number(price);
}
const output = Object.entries(pricesByName)
.map(([currency, total]) => ({ currency, total }));
console.log(output);
If the currency or price is null, ignore them using an if
statement:
var products = [
{
'id': '1',
'currency': { id:1, name:'Dollar' },
'price': '100'
}, {
'id': '4',
'currency': { id:2, name:'Euro' },
'price': '300'
}, {
'id': '6',
'currency': { id:1, name:'Dollar' },
'price': '50'
}, {
'id': '16',
'currency': null,
'price': '50'
}, {
'id': '23',
'currency': { id:2, name:'Euro' },
'price': null
}
];
const pricesByName = {};
for (const { currency, price } of products) {
if (price === null || currency === null) continue;
const { name } = currency;
pricesByName[name] = (pricesByName[name] || 0) + Number(price);
}
const output = Object.entries(pricesByName)
.map(([currency, total]) => ({ currency, total }));
console.log(output);
Upvotes: 2