如何通过另一个对象中的键聚合对象值

时间:2021-06-17 21:07:34

标签: javascript arrays object lodash

我有两个对象数组,如下所示:

array1 = [
   { material: "ABC123", cost: 100 },
   { material: "DEF456", cost: 150 }
]

array2 = [
  { material: "ABC123", date: "1/1/20", quantity: 4 },
  { material: "ABC123", date: "1/15/20", quantity: 1 },
  { material: "ABC123", date: "2/15/20", quantity: 3 },
  { material: "ABC123", date: "4/15/21", quantity: 1 },
  { material: "DEF456", date: "3/05/20", quantity: 6 },
  { material: "DEF456", date: "3/18/20", quantity: 1 },
  { material: "DEF456", date: "5/15/21", quantity: 2 }
]

我想创建一个新的对象数组,其中包含来自 array1 的所有键/值对以及每个项目按年份和月份的汇总数量。

结果是:

[
ABC123: {
    cost: 100,
    byYear: {
        2020: {
            byMonth: { 
                1: 5,
                2: 3
            }
        }
    },
    {
        2021: {
            byMonth: {
                4: 1
            }
        }
    },
},
DEF456: {
    cost: 150,
    byYear: {
        2020: {
            byMonth: {
                3: 7,
                2: 3
            }
        }
    },
    {
        2021: {
            byMonth: {
                5: 2
            }
        }
    },
}
]

以下是迄今为止我的代码,使用 lodash,但这在解决方案中不是必需的。我遇到的问题是,在每个项目下创建的年份和月份键并非特定于该项目。每个项目都获得所有项目存在的年和月键。

let itemSummary = {};

 _.forEach(array1, function (item) {
    itemSummary[item["material"]] = itemSummary[item["material"]] || {}; // create material as key
    itemSummary[item["material"]] = item; // add props from array1 under each key

    _.forEach(array2, function (trans) {
      // iterate through transactions and aggregate by year and month
      let transactionYear = new Date(trans["date"]).getFullYear();
      let transactionMonth = new Date(trans["date"]).getMonth() + 1;
      itemSummary[item["material"]]["byYear"] = itemSummary[item["material"]]["byYear"] || {}; //create year key
      itemSummary[item["material"]]["byYear"][transactionYear] = itemSummary[item["material"]]["byYear"][transactionYear] || {}; // set year key

      itemSummary[item["material"]]["byYear"][transactionYear]["byMonth"] = itemSummary[item["material"]]["byYear"][transactionYear]["byMonth"] || {};
      itemSummary[item["material"]]["byYear"][transactionYear]["byMonth"][transactionMonth] =
        itemSummary[item["material"]]["byYear"][transactionYear]["byMonth"][transactionMonth] || {};
    });
  });

显然,这并没有像我上面提到的那样汇总数量,因为我首先需要为每个项目获取正确的年份和月份键。

非常感谢任何帮助

2 个答案:

答案 0 :(得分:2)

您可以对所有分组和另一个对象采用动态方法来获得最终总和。

const
    array1 = [{ material: "ABC123", cost: 100 }, { material: "DEF456", cost: 150 }],
    array2 = [{ material: "ABC123", date: "1/1/20", quantity: 4 }, { material: "ABC123", date: "1/15/20", quantity: 1 }, { material: "ABC123", date: "2/15/20", quantity: 3 }, { material: "ABC123", date: "4/15/21", quantity: 1 }, { material: "DEF456", date: "3/05/20", quantity: 6 }, { material: "DEF456", date: "3/18/20", quantity: 1 }, { material: "DEF456", date: "5/15/21", quantity: 2 }],
    groups = [
        ({ material }) => material,
        _ => 'byYear',
        ({ date }) => '20' + date.split('/')[2],
        _ => 'byMonth'
    ],
    sum = {
        key: ({ date }) => date.split('/')[0],
        value: ({ quantity }) => quantity
    }, 
    result = array2.reduce(
        (r, o) => {
            const temp = groups.reduce((t, fn) => t[fn(o)] ??= {}, r);
            temp[sum.key(o)] ??= 0;
            temp[sum.key(o)] += sum.value(o);
            return r;
        },
        Object.fromEntries(array1.map(({ material, cost }) => [material, { cost }]))
    );

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

如有必要,您可以添加进一步的分组并添加一个函数数组以进行最终聚合。

const
    array1 = [{ material: "ABC123", cost: 100 }, { material: "DEF456", cost: 150 }],
    array2 = [{ material: "ABC123", date: "1/1/20", quantity: 4 }, { material: "ABC123", date: "1/15/20", quantity: 1 }, { material: "ABC123", date: "2/15/20", quantity: 3 }, { material: "ABC123", date: "4/15/21", quantity: 1 }, { material: "DEF456", date: "3/05/20", quantity: 6 }, { material: "DEF456", date: "3/18/20", quantity: 1 }, { material: "DEF456", date: "5/15/21", quantity: 2 }],
    groups = [
        ({ material }) => material,
        _ => 'byYear',
        ({ date }) => '20' + date.split('/')[2],
        _ => 'byMonth',
        ({ date }) => date.split('/')[0]
    ],
    ultimately = [
        (target, { quantity }) => {
            target.sum ??= 0;
            target.sum += quantity;
        },
        (target, { quantity }) => {
            target.max ??= quantity;
            if (quantity > target.max) target.max = quantity;
        }
    ],
    result = array2.reduce(
        (r, o) => {
            const temp = groups.reduce((t, fn) => t[fn(o)] ??= {}, r);
            ultimately.forEach(fn => fn(temp, o));
            return r;
        },
        Object.fromEntries(array1.map(({ material, cost }) => [material, { cost }]))
    );

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

答案 1 :(得分:1)

可能有多种方法可以达到您想要的格式,这里是其中之一。我选择以 array1 为基础,因为它看起来像主列表,但你也可以以 array2 为基础。

我们从 array1 中取出每一项,从 array2 中过滤匹配的材料,然后通过 reduce 运行它们,在那里我们解析日期并构建对象来存储 byYear 和 byMonth

array1 = [
   { material: "ABC123", cost: 100 },
   { material: "DEF456", cost: 150 }
]

array2 = [
  { material: "ABC123", date: "1/1/20", quantity: 4 },
  { material: "ABC123", date: "1/15/20", quantity: 1 },
  { material: "ABC123", date: "2/15/20", quantity: 3 },
  { material: "ABC123", date: "4/15/21", quantity: 1 },
  { material: "DEF456", date: "3/05/20", quantity: 6 },
  { material: "DEF456", date: "3/18/20", quantity: 1 },
  { material: "DEF456", date: "5/15/21", quantity: 2 }
]

let data = array1.reduce((b, a) => {
    let thing = a.hasOwnProperty('material') ? 'material' : 'part';
    let items = array2.filter(e => e.material === a[thing]).reduce((d, c) => { 
          let dt = c.date.split("/"),
            m = dt[0],
            y = new Date(dt).getFullYear()
          if (d.hasOwnProperty(y)) {
            if (d[y].byMonth.hasOwnProperty(m)) d[y].byMonth[m]+= +c.quantity;
            else d[y].byMonth[m] = c.quantity;
          } else d[y] = {byMonth: {[m]: c.quantity}};
        return d
      }, {}) ;

    b[a[thing]] = {
      cost: a.cost,
      byYear: items
    };
    return b
  }, {})
  
  console.log(data)

相关问题