使用嵌套的reduce函数对一个对象按属性进行操作

3

我有这样的数据:

const data = [
  { id: 1, cat: "human", age: "10" },
  { id: 2, cat: "human", age: "20" },
  { id: 3, cat: "human", age: "10" },
  { id: 4, cat: "animal", age: "10" },
  { id: 5, cat: "animal", age: "20" },
  { id: 6, cat: "animal", age: "10" },
  { id: 7, cat: "alien", age: "10" },
  { id: 8, cat: "alien", age: "20" },
  { id: 9, cat: "alien", age: "10" },
];

我希望能将这些数据分组,就像这样:
const gr = {
  human: {
    all: [
      { id: 1, cat: "human", age: "10" },
      { id: 2, cat: "human", age: "20" },
      { id: 3, cat: "human", age: "10" },
    ],
    ages: {
      "10": [
        { id: 1, cat: "human", age: "10" },
        { id: 3, cat: "human", age: "10" },
      ],
      "20": [
        { id: 2, cat: "human", age: "20" },
      ],
    }
  },
  animal: {...},
  alien: {...},
}

我首先会这样简化它:
const gr = data.reduce((acc, el) => {
  const { cat } = el;
  acc[cat] = acc[cat] || { all: [] };
  acc[cat].all.push(el);

  return acc;
}, {});

但我不能在这里使用嵌套的reduce。我可以像这样分开做:

const grAge = gr.human.all.reduce((acc,el) => {
    const {age} = el;
    acc[age] = acc[age] || [];
    acc[age].push(el);

    return acc;
  },{});

gr.human["ages"] = grAge;

但是显然,这并不是很高效,需要更多的工作。或许可以像这样做:
Object.keys(gr).forEach(key => {
  const grAge = gr[key].all.reduce((acc,el) => {
    const {age} = el;
    acc[age] = acc[age] || [];
    acc[age].push(el);

    return acc;
  },{});

  gr[key]["ages"] = grAge;
});

我能否将这些reduce操作合并为一步?

如果有其他好的方法,我可以使用它们,不需要使用reduce方法。


你能具体说明你期望的是什么吗?因为“类似于”并不是一个明确的术语。 - Vadim Hulevich
1
答案已经在里面了。请看下面“类似于那样”的短语下面的 const gr = ... - devserkan
你必须单独完成它。没有任何东西可以使其内联嵌套。但也许你应该创建具有有意义名称的单独函数。 - Oliver
感谢@Oliver的建议。我已经在这里纠结了一段时间。 - devserkan
2个回答

3
你可以采用单循环方法,将所需的结构分配给all或嵌套结构中。
如果你想获得更动态的版本,需要简化每个嵌套级别的结果结构(这意味着年龄级别将包含一个all属性)。

const
    data = [{ id: 1, cat: "human", age: "10" }, { id: 2, cat: "human", age: "20" }, { id: 3, cat: "human", age: "10" }, { id: 4, cat: "animal", age: "10" }, { id: 5, cat: "animal", age: "20" }, { id: 6, cat: "animal", age: "10" }, { id: 7, cat: "alien", age: "10" }, { id: 8, cat: "alien", age: "20" }, { id: 9, cat: "alien", age: "10" }],
    result = data.reduce((r, o) => {
        r[o.cat] = r[o.cat] || { all: [], ages: {} };        
        r[o.cat].all.push(o);
        r[o.cat].ages[o.age] = r[o.cat].ages[o.age] || [];
        r[o.cat].ages[o.age].push(o);
        return r;
    }, {});

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


很棒的答案。从中学到了reduce的另一种用法。 - Alex Pappas
我希望能在这里看到你 :) 做得好,谢谢。奇怪的是,我发誓我试过类似的东西。当我看到你的代码时,我说:“哦,我试过那个。”现在我找不到问题出在哪里了,但是你的代码解决了我的问题。 - devserkan

1
另一种方法是使用Sets获取每个唯一的类别和年龄,然后将它们缩减为最终的JSON:
编辑:似乎Stack Overflow片段不喜欢它,但在浏览器控制台中执行它将给出正确的结果。

const data = [
 { id: 1, cat: "human", age: "10" },
 { id: 2, cat: "human", age: "20" },
 { id: 3, cat: "human", age: "10" },
 { id: 4, cat: "animal", age: "10" },
 { id: 5, cat: "animal", age: "20" },
 { id: 6, cat: "animal", age: "10" },
 { id: 7, cat: "alien", age: "10" },
 { id: 8, cat: "alien", age: "20" },
 { id: 9, cat: "alien", age: "10" },
];

const output = [...new Set(data.map(thing => thing.cat))].reduce((acc, category) => {
 const catData = data.filter(thing => thing.cat === category)

 return {
  [category]: {
   all: catData,
   ages : [...new Set(catData.map(catThing => catThing.age))].reduce((catAcc, age) => ({
    [age]: [...catData.filter(catThing => catThing.age === age)],
    ...catAcc
   }), {})
  },
  ...acc
 }
}, {})

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


顺便提一下,JSON 总是一个字符串。结构只是一个包含其他对象或数组的对象。 - Nina Scholz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接