类型 'null' 不能用作 Array.reduce() 累加器中的索引类型



这是一个后续问题:如何将具有许多键值对的对象列表转换为一个大的嵌套对象?

最初的目标是将具有许多键值对的对象列表转换为一个大的嵌套对象。

例如,从这里:

const items = [
{
"id": 3,
"orgId": 2,
"mod": "toyota",
"part": "wheel",
"price": 333
},
{
"id": 4,
"orgId": 2,
"mod": "toyota",
"part": "shell",
"price": 350
},
{
"id": 9,
"orgId": 2,
"mod": "honda",
"part": "wheel",
"price": 222
},
{
"id": 10,
"orgId": 2,
"mod": "honda",
"part": "shell",
"price": 250
}
]

转换为:


items = {
"toyota": {"wheel": 333, "shell": 350 }, 
"honda": {"wheel": 222, "shell": 250 }
}
下面的代码在Javascript中工作:
const transformedItems = items.reduce((acc, item) => {
acc[item.mod] = { ...acc[item.mod], [item.part]: item.price }
return acc
}, {})
console.log(transformedItems)

如何,我想把这个逻辑放到服务器端(用Typescript编写),代码不能编译:

/Users/john/tmp/dolphin/api/node_modules/ts-node/src/index.ts:293
return new TSError(diagnosticText, diagnosticCodes)
^
TSError: ⨯ Unable to compile TypeScript:
src/utils/billingFunctions.ts:52:11 - error TS2538: Type 'null' cannot be used as an index type.
52       acc[item.mod] = { ...acc[item.mod], [item.part]: item.price }
~~~~~~~~~~~~~
src/utils/billingFunctions.ts:52:37 - error TS2538: Type 'null' cannot be used as an index type.
52       acc[item.mod] = { ...acc[item.mod], [item.part]: item.price }
~~~~~~~~~~~~~
src/utils/billingFunctions.ts:52:53 - error TS2464: A computed property name must be of type 'string', 'number', 'symbol', or 'any'.
52       acc[item.mod] = { ...acc[item.mod], [item.part]: item.price }
~~~~~~~~~~~~~~~
at createTSError (/Users/john/tmp/dolphin/api/node_modules/ts-node/src/index.ts:293:12)
at reportTSError (/Users/john/tmp/dolphin/api/node_modules/ts-node/src/index.ts:297:19)
at getOutput (/Users/john/tmp/dolphin/api/node_modules/ts-node/src/index.ts:399:34)
at Object.compile (/Users/john/tmp/dolphin/api/node_modules/ts-node/src/index.ts:457:32)
at Module.m._compile (/Users/john/tmp/dolphin/api/node_modules/ts-node/src/index.ts:530:43)
at Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
at Object.require.extensions.<computed> [as .ts] (/Users/john/tmp/dolphin/api/node_modules/ts-node/src/index.ts:533:12)
at Module.load (internal/modules/cjs/loader.js:928:32)
at Function.Module._load (internal/modules/cjs/loader.js:769:14)
at Module.require (internal/modules/cjs/loader.js:952:19)
[nodemon] app crashed - waiting for file changes before starting...

然而,当我尝试:

const transformedItems = items.reduce((acc, item) => {
console.log(acc, item.mod)  // print out
acc[item.mod] = { ...acc[item.mod], [item.part]: item.price }
return acc
}, {})

控制台日志打印正常:item.mod为字符串。为什么它抱怨它是Type 'null'?

当使用reduce时,您通常必须手动提供累积值的类型,特别是当您传入诸如空对象{}之类的东西时。可以在形参处添加类型断言,也可以使用函数的泛型形参:

const transformedItems = items.reduce<Record<string, Record<string, number>>>(
(acc, item) => {
acc[item.mod] = { ...acc[item.mod], [item.part]: item.price }
return acc
}, {})

如果items变量是动态传递的,并且缺少类型信息,你可能还需要定义它的类型,例如使用接口:

interface Item
{
id: number;
orgId: number;
mod: string;
part: string;
price: number;
}

可以内联或在此之前使用:

const transformedItems = (items as Item[]).reduce(...

我认为你可以尝试的一个解决方案是强迫item.mod,以便TypeScript也确定它是一个字符串。你只需要像这样在String(...)中包装它:

const transformedItems = items.reduce((acc, item) => {
console.log(acc, item.mod)  // print out
acc[item.mod] = { ...acc[String(item.mod)], [item.part]: item.price }
return acc
}, {})

我认为这不是最typescript的方式。

您应该确保itemsItem的数组,而不是any的数组。当像你的例子那样编写你的items数组时,TypeScript可以自动推断出你所有的item都有一个mod字段,这个字段是一个字符串,因为所有的例子都有一个。我怀疑您正在将项目作为参数传递给函数(或动态填充它),在这种情况下,您应该自己定义项目的类型。

这应该会让TypeScript毫无疑问地知道items数组中的每个条目都有一个mod和一个part字段,而且这个字段肯定是字符串。

type Item = {
id: number;
orgId: number;
mod: string;
part: string;
price: number;
};
function getTransformedItems(items: Item[]) {
return items.reduce((acc, item) => {
acc[item.mod] = { ...acc[item.mod], [item.part]: item.price };
return acc;
}, {} as Record<string, Record<string, number>>);
}

另一种更经典的方式是

让我们添加类型,这将使转换更容易:

interface CommonData {
id: number;
orgId: number;
mod: string;
part: string;
price: number;
}
interface PartDetails {
name: string;
price: number;
}
interface TransformedData {
carModel: string;
details: Array<PartDetails>
}

转换逻辑:

function transformData(data: Array<CommonData>): Array<TransformedData> {
const transformedData: Array<TransformedData> = [];
data.forEach((item: CommonData) => {
const index = transformedData.findIndex(transformedItem => transformedItem.carModel === item.mod);

if (index === -1) {
transformedData.push({
carModel: item.mod,
details: [
{
name: item.part,
price: item.price
}
]
})
} else {
const existingTransform = transformedData[index];
existingTransform.details.push({
name: item.part,
price: item.price
});
}
});
return transformedData;
}

输入

[
{
"id": 3,
"orgId": 2,
"mod": "toyota",
"part": "wheel",
"price": 333
},
{
"id": 4,
"orgId": 2,
"mod": "toyota",
"part": "shell",
"price": 350
},
{
"id": 9,
"orgId": 2,
"mod": "honda",
"part": "wheel",
"price": 222
},
{
"id": 10,
"orgId": 2,
"mod": "honda",
"part": "shell",
"price": 250
}
]

输出
[
{
"carModel": "toyota",
"details": [
{
"name": "wheel",
"price": 333
},
{
"name": "shell",
"price": 350
}
]
},
{
"carModel": "honda",
"details": [
{
"name": "wheel",
"price": 222
},
{
"name": "shell",
"price": 250
}
]
}
] 

根据错误信息,item.mod字段中有一条记录没有任何值

类型'null'不能用作acc[item]的索引类型。国防部]=

解决方案取决于你打算如何处理这样的记录。

如果您想保留它,那么将item.mod转换为String,如其他答案所建议的。

如果您想省略它,则需要添加如下检查

const transformedItems = items.reduce((acc, item) => {
if (item.mod) {
acc[item.mod] = { ...acc[item.mod], [item.part]: item.price }
}
return acc
}, {})

在这两种情况下,似乎你正在处理的数据是不一致的,必须在使用前进行验证。

最新更新