如果你真的想使用类和 Map 对象,你可以,但使用函数和普通的旧 javascript 对象会更直接。
我将展示一种方法,您可以根据需要对其进行调整(但请注意,TypeScript 中的Map 没有按照您想要的方式进行强类型化,因此我会避免使用它)。
这是mapKeys()函数:
function mapKeys<D extends object, M extends Record<keyof M, keyof D>>(
data: D,
dataMap: M
) {
const ret = {} as { [K in keyof M]: D[M[K]] };
(Object.keys(dataMap) as Array<keyof M>).forEach(
<K extends keyof M>(k: K) => {
ret[k] = data[dataMap[k]];
}
);
return ret;
}
这是它的基本用法:
const mapped = mapKeys(
{
Name: "Dudi",
Age: 123
},
{ name: "Name", age: "Age" }
);
// const mapped: {
// name: string;
// age: number;
// }
console.log(mapped); // {name: 'Dudi', age: 123}
mapKeys() 在运行时的工作方式是简单地循环遍历 dataMap 的键 k 并将返回对象中的类似属性设置为来自 data 键 dataMap[k] 的数据。这就是ret[k] = data[dataMap[k]] 行。
剩下的大部分是类型注解和断言来帮助编译器理解和验证我们在做什么。假设data 的类型为D,dataMap 的类型为M,则返回类型将为mapped type{[K in keyof M]: D[M[K]] }。这就是说它将具有与M 相同的键,并且每个键K 的值将是D[M[K]] 类型(即我们look up K 的K 属性M,并且然后查找D 的那个属性)。
所以这应该适合你,具体取决于你的用例。
当然有很多注意事项;我试图询问您在其中一些情况下想看到什么,我所知道的是,如果它不是 data 的键,您不希望在 dataMap 中放置一个值。 constraint M extends Record<keyof M, keyof D> 应该保证这一点。但还有其他可能的极端情况:
强输入dataMap
首先,dataMap 参数必须是相当强的类型才能工作。如果你这样做:
const source = { Name: "Didu", Age: 321 };
const map = { name: "Name", age: "Age" };
然后map 最终将被推断为类型{name: string, age: string} 而不是{name: "Name", age: "Age"}。你会得到一个错误:
const badInfer = mapKeys(source, map); // error!
// ┌───────────────────────────> ~~~
// └─ Argument of type '{ name: string; age: string; }' is not assignable
// to parameter of type 'Record<"name" | "age", "Name" | "Age">'.
解决此问题的方法是确保dataMap 具有string literal 属性值,例如您在使用const assertion 时获得的值:
const fixedMap = { name: "Name", age: "Age" } as const; //
// inferred to be {readonly name: "Name"; readonly age: "Age"; }
const fixedInfer = mapKeys(source, fixedMap); // okay, right type now
请注意,如果我们使用Map 而不是对象,default TypeScript type declarations for it 不够强大。 Map<K, V> 意味着K 中的任何键都可能映射到V 中的任何值。所以new Map([["name","Name"],["age","Age"]]) 充其量最终会等同于{name: "Name" | "Age", age: "Name" | "Age"} 类型,而你的输出类型中会出现一个不幸的string | number。
省略属性
没有什么能阻止您将data 中的一些键留在dataMap 之外:
const fewerProps = mapKeys({ Name: "Dodi", Age: 132 }, { name: "Name" });
// const fewerProps: { name: string }; // no age
console.log(fewerProps); // {name: "Dodi"}
重复的属性
没有什么能阻止您在data 中多次映射dataMap 中的同一个键:
const duplicateProps = mapKeys(
{ Name: "Dido", Age: 231 },
{
name: "Name",
alsoName: "Name",
age: "Age",
alsoAge: "Age"
}
);
// const duplicateProps: {name: string; alsoName: string; age: number; alsoAge: number;}
console.log(duplicateProps); // {name: "Dido", alsoName: "Dido", age: 231, alsoAge: 231}
索引签名会做奇怪的事情
如果你的data 对象有一个index signature,那么类型系统的行为会很奇怪,因为它会假设data 有任何可能与索引匹配的键:
const dictionary: { [k: string]: string | number } = { Name: "Didi", Age: 312 };
const confused = mapKeys(dictionary, {
dogs: "Dogs",
cats: "Cats",
age: "Age"
});
// const confused: { dogs: string | number; cats: string | number; age: string | number;}
console.log(confused); // {dogs: undefined, cats: undefined, age: 312 }
// note that "undefined" can sneak into index signatures
这不是一个完美的结果,因为类型系统说confused 有一个dogs 类型为string | number 的属性,但在运行时它实际上是undefined。这是索引签名的陷阱之一,我们的映射器就陷入其中。
如果您决定要查看什么并放入更复杂的类型断言和注释,则可以解决其中的一些警告,但我想我会在这里停下来,并将任何修补工作留给您。这应该足以给你一些方向。祝你好运!
Link to code