如何通过嵌套值将嵌套对象数组分组为多个数组

时间:2018-02-18 19:00:37

标签: javascript arrays sorting grouping

我正在尝试按值将数组转换为一组新数组,在本例中为id

输入

let array = [
  {"item": {"id": 111, "name": "item1"}, "qty": 1}, 
  {"item": {"id": 222, "name": "item2"}, "qty": 2},
  {"item": {"id": 222, "name": "item3"}, "qty": 3}
];

所需输出

let newArray = [
  [{"item": {"id": 111, "name": "item1"}, "qty": 1}], 
  [{"item": {"id": 222, "name": "item2"}, "qty": 2},
   {"item": {"id": 222, "name": "item3"}, "qty": 3}]
];

使用标准groupBy函数,我们可以返回按id排序的两个数组。

function groupItemBy(array, property) {
  var hash = {};
  for (var i = 0; i < array.length; i++) {
    if (!hash[array[i][property]]) hash[array[i][property]] = [];
    hash[array[i][property]].push(array[i]);
  }
  return hash;
}

但是,在尝试将这些数据映射到新数组时,嵌套的qty数据将丢失。

function parse() {
  let tmp = Object.values(groupItemBy(array.map(el => el.item), "id"));

  tmp.forEach(item => {
    console.log(item);
    // do something with each item in array
  })
}

实际输出

let newArray = [
  [{{"id": 111, "name": "item1"}], 
  [{"id": 222, "name": "item2"},
   {"id": 222, "name": "item3"}]
];

在将原始数组分组为排序数组数组时,如何保持关联数据的完整性?

3 个答案:

答案 0 :(得分:2)

为此,您必须以某种方式告诉函数key属性所在的位置。可以想象非常复杂的嵌套对象,并且有些可能具有相同的属性名称,因此如果没有这样的规范,它甚至可能导致歧义。

解决这个问题的一种方法是让函数知道点分隔属性(在一个字符串中) - 一种“路径”。在你的情况下,item.id。使用该信息,该函数可以知道在哪里查找id值(在嵌套对象item中)。

显然,该函数会将这些字符串拆分掉。然后,它可以对生成的属性名称数组执行reduce,以找到数组中每个对象的键值。

以下是这样的看法:

let cart = [{"item": {"id": 111,"name": "item1", }, "qty": 10,}, {"item": {"id": 222,"name": "item2"},"qty": 1}, {"item": {"id": 222,"name": "item3"},"qty": 1,}];

function groupItemBy(array, property) {
    var hash = {},
        props = property.split('.');
    for (var i = 0; i < array.length; i++) {
        var key = props.reduce(function(acc, prop) {
            return acc && acc[prop];
        }, array[i]);
        if (!hash[key]) hash[key] = [];
        hash[key].push(array[i]);
    }
    return hash;
}

let grouped = Object.values(groupItemBy(cart, 'item.id'));
console.log(grouped);
.as-console-wrapper { max-height: 100% !important; top: 0; }

答案 1 :(得分:1)

您可以尝试使用reduce检查,如果您已经看过当前的密钥,如果没有 - 使用empry数组作为值创建它,然后将整个当前对象推送到那里。然后你需要得到结果的值,你很高兴。

Object.values(arr.reduce((result, obj) => {
  (result[obj.item.id] || (result[obj.item.id] = [])).push(obj);
  return result;
}, {}));

或者,您可以使用Lodash和groupBy,然后您将拥有:

Object.values(_.groupBy(arr, "item.id")));

使用此bin来播放控制台中的结果 - https://codesandbox.io/s/qx14372v74

答案 2 :(得分:1)

摘要

const transform = (array) => Object.values(array.reduce(
  (all, curr) => {
    const key = curr.item.id;
    (all[key] || (all[key] = [])).push(curr);
    return all;
  }, {}
))

使用库

使用Ramda(免责声明:我是Ramda作者),我会这样做,不用考虑两次:

const transform = compose(values, groupBy(path(['item', 'id'])))
const results = transform(array)

转换图书馆代码

假设我们不想为这个问题包含一个库,我将逐步从该解决方案开始。我们首先需要删除compose,只需两个函数即可轻松完成。我们首先添加一个显式参数:

const transform = (array) => compose(values, groupBy(path(['item', 'id'])))(array),

然后将这两个调用包装在compose

const transform = (array) => Object.values(groupBy(path(['item', 'id']), array))

然后我们可以轻松地将path(['item', 'id'])替换为obj => obj.item.id。请注意,我们在这里失去了一点安全感。如果没有item属性,我们可以添加例外。但安全地做它会增加更多的机器。

const transform = (array) => Object.values(groupBy(obj => obj.item.id, array))

然后我们可以写一个简单版本的groupBy

const groupBy = (fn) => (list) => list.reduce(
  (all, curr) => {
    const key = fn(curr);
    (all[key] || (all[key] = [])).push(curr);
    return all;
  },
  {}
)

并用它替换Ramda groupBy,用我们喜欢的值折叠简单的path替换:

const transform = (array) => Object.values(array.reduce(
  (all, curr) => {
    const key = (obj => obj.item.id)(curr);
    (all[key] || (all[key] = [])).push(curr);
    return all;
  }, {}
))

稍微简化,我们得到

const transform = (array) => Object.values(array.reduce(
  (all, curr) => {
    const key = curr.item.id;
    (all[key] || (all[key] = [])).push(curr);
    return all;
  }, {}
))

const array = [{"item": {"id": 111, "name": "item1"}, "qty": 1}, {"item": {"id": 222, "name": "item2"}, "qty": 2}, {"item": {"id": 222, "name": "item3"}, "qty": 3}];

console.log(transform(array));

构建我们自己的迷你库

或者,我们实际上可以创建那些Ramda函数的等价物,因为它们可以以各种方式使用:

const groupBy = (fn) => (list) => list.reduce(
  (all, curr) => {
    const key = fn(curr);
    (all[key] || (all[key] = [])).push(curr);
    return all;
  },
  {}
)
const values = obj => Object.values(obj);
const pipe = (f1, ...fns) => (...args) => {
  return fns.reduce((res, fn) => fn(res), f1.apply(null, args));
};
const path = (nodes) => (obj) => nodes.reduce((o, node) => o[node], obj)


const transform = pipe(groupBy(path(['item', 'id'])), values)

const array = [{"item": {"id": 111, "name": "item1"}, "qty": 1}, {"item": {"id": 222, "name": "item2"}, "qty": 2}, {"item": {"id": 222, "name": "item3"}, "qty": 3}]

console.log(transform(array))

(请注意,我从compose切换到pipe,只是因为它更容易快速实现。它们具有相同的行为,但是以相反的顺序获取其功能列表。)