通过动态属性从对象数组创建对象

3

我有一个对象数组

  "options": [
    {
      "width": 10,
      "height": 20
    },
    {
      "width": 20,
      "height": 40
    },
    {
      "width": 30,
      "height": 60
    }
  ]

That I want convert to the following

{ width: [10, 20, 30], height: [20, 40, 60] }

请记住,这些键是动态的。在此示例中,它们是widthheight

我确实有解决方案。

const pluck = (arr: Record<string, any> | undefined, property: string) => {
  return arr?.map((obj: any) => {
    return obj[property];
  });
};
const unique = (arr: any[] | undefined) =>
  arr ? Object.keys(Object.assign({}, ...arr)) : null;

const keys = unique(myArray);
const options = keys
  ? Object.fromEntries(keys.map((key) => [key, pluck(myArray, key)]))
  : null;

但我能不能把这个变得更短?


1
这是一段漂亮的代码,你为什么想让它更短? - Danial
@Danial 谢谢!我只是有一种感觉,它可以做得更好,也许使用 reduce。 - Unknown
不要为了缩短代码而去追求,因为你的应用用户并不关心这一点,相反,要找到最具响应性的代码。 - Hanna Rose
4个回答

3

最简单的方法就是写一个循环。让我们先从JavaScript开始,然后再回到类型:

const result = {};
for (const entry of myArray) {
    for (const key in entry) { // or: of Object.keys(entry)
        const values = result[key] = result[key] ?? [];
        values.push(entry[key]);
    }
}

实时示例:

const myArray = [
    {
        "width": 10,
        "height": 20
    },
    {
        "width": 20,
        "height": 40
    },
    {
        "width": 30,
        "height": 60
    }
];

const result = {};
for (const entry of myArray) {
    for (const key in entry) { // or: of Object.keys(entry)
        const values = result[key] = result[key] ?? [];
        values.push(entry[key]);
    }
}
console.log(result);
.as-console-wrapper {
    max-height: 100% !important;
}

关于 for (const key in entry)for (const key of Object.keys(entry),前者还会访问继承属性。你的示例中没有任何继承属性,但如果有,你可以添加一个 hasOwnProperty 检查,或者如果不介意使用临时数组,可以使用 of Object.keys(entry)

好了,接下来是类型。我本来以为这会很困难,但结果并不是,这让我有些怀疑自己是否遗漏了什么。我们将使用函数来实现它,这样我们就可以使用类型参数:

function condense<ObjType extends object>(array: ObjType[]) {
    const result: Record<string, any> = {};
    for (const entry of array) {
        for (const key in entry) { // or: of Object.keys(entry)
            const values = result[key] = result[key] ?? [];
            values.push(entry[key]);
        }
    }
    return result as {[key in keyof ObjType]: ObjType[key][]};
}
const result = condense(myArray);
console.log(result);

装置

由于结果是逐步建立的,我在condense函数中使用了相当宽松的类型,但输入和输出是正确的类型。例如,上面的result的结果是{ width: number[]; height: number[]; },这正是我们想要的。甚至如果我们加入第三个不同类型(例如string)的属性(例如description),它也可以工作,并给出{ width: number[]; height: number[]; description: string[]; }Playground链接
目前的潮流是使用reduce,但在这里使用它没有任何理由,它只会增加复杂性。

2
他说:“记住键是动态的。” - Danial
@T.J.Crowder 你好 T.J,能否添加 TypeScript 支持? - Unknown
@Unknown - 抱歉,我不明白我是如何错过你问题中的多个基本要素(键是动态的,你正在使用TypeScript)。这两点都非常明显,但是...我会尝试解决,但是类型方面确实非常复杂。也许我只需要删除答案。但是让我试一试。 :-) - T.J. Crowder
@Unknown - 您当前的结果类型为{[k: string]: any} | null。这相对容易复制,但我觉得这样做有些作弊。 :-) 要实际拥有一个带类型的结果,需要将输入数组视为as const - T.J. Crowder
@T.J.Crowder,您真是太棒了,没有什么需要道歉的。我使用了 Record<string, any> | null,但它仍然抱怨这部分 values.push(entry[key])。"Element implicitly has an 'any' type because expression of type 'string' can't be used to index type"。我会自己尝试一下。谢谢! - Unknown
1
@Unknown - 谢谢!考虑到我读错问题的严重程度,你非常友善。:-) 结果比我想象的要容易,也不需要 as const。我已经更新了答案。 - T.J. Crowder

0

我使用for循环,因为它比map或reduce更具响应性和速度。

  const options = [
    { "width": 10, "height": 20 },
    { "width": 20, "height": 40 },
    { "width": 30, "height": 60 }
  ];
//That I want convert to the following
//{ width: [10, 20, 30], height: [20, 40, 60] }

let newobj = new Object();
for( let a of options ){
  for( let b of Object.keys(a) ){
    if( !newobj[b] ) newobj[b]=[]; newobj[b].push(a[b]);
  }
}
console.log(newobj)


1
我使用for循环,因为它比map或reduce更响应和更快。速度/性能根本不是问题。因此,我建议熟悉所有数组方法以及基于Object[keys|values|entries]的对象迭代。这些方法消耗函数。因此,对于更复杂的数据结构,可以编写可重用的代码来执行重复任务,例如数据转换和树遍历。如果这些函数也以精确且易于理解的方式命名,则可以帮助其他开发人员更轻松地理解和维护代码。 - Peter Seliger

-1

请查看 Array.prototype.reduce()以获取更多信息。

const input = [
  { width: 100, height: 100 },
  { width: 200, height: 200 },
  { width: 300, height: 300 },
];

const group = (someArrayOfObjects) => {
  return someArrayOfObjects.reduce((acc, current) => {
    Object.entries(current).forEach(([key, value]) => {
      if (!acc[key]) {
        acc[key] = [];
      }
      acc[key].push(value);
    });
    return acc;
  }, {});
};

console.log(group(input))


2
这里没有使用reduce的必要,只需要一个循环即可。 - T.J. Crowder
嗯,有趣。出于好奇,你推荐使用for循环的特别原因是什么?@T.J.Crowder - Arman Charan
reduce 在使用预定义的可重用 reducer 进行函数式编程时非常好用。如果您至少更改了累加器(例如,对数组求和),那么它就不是很好用了。除此之外,它只是一种更复杂、更难写、更难读的循环写法。(顺便说一句,这不仅是我的看法,还有像 Jake ArchibaldBrian Terlson 这样的人也持相同观点,...) - T.J. Crowder
1
哈哈,说得好。也许这只是一时的风尚。我个人认为for更加复杂,而单独使用let有点不雅观。话虽如此,你在这个问题中给出的例子实在是太好了。感谢你展示了for的强大之处。 - Arman Charan
1
Arman,像你这样倾听(即使最终不同意)而不是盲目反应的人让SO变得更好。谢谢!祝编码愉快! - T.J. Crowder

-1

var height = [], width = [];
options.forEach((op) =>{
  height.push(op.height)
  width.push(op.width)
})
   var returnValue = {width: width, height: height}


2
请注意,这些键是动态的。 - Andy

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