【问题标题】:How to group an array of objects by key and sums a nested object property如何按键分组对象数组并对嵌套对象属性求和
【发布时间】:2020-03-23 02:03:06
【问题描述】:

我有一个产品对象数组,每个产品除了代表所用货币的嵌套对象外还包含产品价格。

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
}
];

我想要做的是获得一个数组,其中每个货币都有一个唯一的对象及其价格总和,最好使用 lodash 或 ES6 脚本。结果我需要的是:

var subtotals = [
    {currency:'Dollar', total: 150},
    {currency:'Euro', total: 300}
]

我尝试了很多样品,但到目前为止没有运气。我的方法是这样,但它返回数组中的整个产品对象,这与预期结果相差甚远

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}) }), {})

结果

{
    "Euro":[
        {"currency":"Euro","price":"300"}
    ],
    "Dolar":[
        {"currency":"Dolar","price":"100"},
        {"currency":"Dolar","price":"50"}
    ]
}

请注意,有些产品一开始可能没有货币,因为它的价值来自下拉输入。在这种情况下,可以在结果中省略该值。

任何帮助将不胜感激。

【问题讨论】:

  • @CertainPerformance 我已经编辑了代码,只是一个错字。我需要用相同的货币对对象求和,根本没有减法。

标签: javascript arrays object grouping lodash


【解决方案1】:

假设您想将总数相加并得到 300 欧元的总数:

遍历每个currency.nameprice,将输入转换为一个对象,其键是货币名称,值是迄今为止找到的该货币名称的累积价格。最后,映射对象的条目以获得所需格式的对象数组:

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 语句忽略它们:

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);

【讨论】:

  • 看起来不错,但是当产品没有货币对象时会失败。请注意,某些产品最初可能没有货币对象。这段代码实际上用在 vuex getter 中,当用户填写表单时它会发生变化,所以价格也可能是 0 或空值
  • 您的示例输入中的所有对象都有这样的对象。当没有对象时你想要什么样的逻辑,你想要在那个迭代中不做任何事情吗?
  • 我再次编辑了代码,对于马虎感到抱歉。那些包含空货币对象或空价格的对象可以在结果中忽略,因为它们不会影响总和。
【解决方案2】:

使用 lodash,您可以使用 _.groupBy() 按货币对象的 name 属性进行分组。然后将结果对象映射到对象数组。您映射到的对象可以使用键作为货币和分组数组的总和(使用_.sumBy())作为总数:

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>

编辑因为你已经改变了你的问题。您需要使用一些替代代码来获得您想要的结果:

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>

【讨论】:

  • 非常好的方法,有什么方法可以忽略那些带有 null 货币对象或 null/0 价格的对象?当它发生时,它会返回一个数组对象{currency:"undefined",total:123456}
  • @Luciano 您可以过滤掉任何不具有currency.name 属性的对象(null 的价格将被视为0,因为一元加运算符+price)跨度>
  • 假设 product.currency 还可以包含(或不包含)嵌套对象“current_quotation”,其中包含“quotation”属性,我如何从 getSubTotals() 返回一个附加属性作为 (总计 * currency.current_quotation.quotation) ?这可能吗?
  • 另外,如果不存在 current_quotation 对象,则附加属性应与总属性值相同
  • @Luciano 我修改了我的代码以添加一个包含total * currency.current_quotation.quotation的附加属性
【解决方案3】:

我在控制台中写了这个,它应该适用于你的情况:

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));
    }
});

【讨论】:

    猜你喜欢
    • 2023-02-07
    • 2021-12-28
    • 2015-11-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-08
    • 2021-08-09
    • 1970-01-01
    相关资源
    最近更新 更多