用于对象重新映射的高级键入



我正在尝试创建一个函数,让调用者在给定特定映射的情况下重新映射对象。生成的对象应该能够知道新的字段名称和类型。

这可能是打字稿吗?我想在这一点上我所需要的只是一个返回类型。这是我的非工作代码:

const mapping = {
a: "learn",
b: "state"
}
const original = {
a: false,
b: 0
}
const map = <
Mapping,
Original
>(
mapping: Mapping,
states: Original
): {
[key: Mapping[key]]: Original[key] //WHAT IS THE CORRECT TYPE HERE?
} => {
return Object.keys(original).reduce((total, key) => {
return {
...total,
[mapping[key]]: original[key] //THERE IS AN ERROR HERE TOO BUT I AM NOT WORRIED ABOUT THAT RIGHT NOW
}
}, {})
}
const remapped = map(mapping, original)
console.log(remapped)
console.log(remapped.learn)
console.log(remapped.state)

我实际上是在尝试将a重命名为learn,将b重命名为state。代码正常工作,但我遇到了一个类型错误("key"指的是一个值,但在这里被用作类型。(。如果有任何帮助,我们将不胜感激!

首先,您的工具箱中需要@jcalz令人惊叹的UnionToIntersection实用程序类型。

type UnionToIntersection<U> = 
(U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never;

现在让我们制作我们自己的实用程序类型:

1    type Remap<A extends { [k: string]: string }, B> =
2        keyof B extends keyof A ?
3        { [P in keyof B & keyof A]: { [k in A[P]]: B[P] } } extends
4        { [s: string]: infer V } ? 
5        UnionToIntersection<V>
6        : never
7        : never;

解释:

  • ln:1,将A的值约束为字符串
  • ln:2,约束所有B的键都存在于A中
  • ln:3,映射到{ "a": {"learn": boolean}, "b": {"state": number} }
  • ln:4,取value部分,该部分变为:{"learn": boolean} | {"state": number}并集
  • n:5,使用UnionToIntersection魔法

把东西放在一起:

const mapping = {
a: "learn",
b: "state",
} as const;   // mark as `const` is necessary to infer value as string literal
// else it'll just be string
const original = {
a: false,
b: 0,
};
const map = <
M extends { [k: string]: string },
O extends { [k: string]: any }
>(mapping: M, original: O): Remap<M, O> => {
return Object.keys(original).reduce((total, key) => {
return {
...total,
[mapping[key]]: original[key]
}
}, {} as any); // as any to mute error
};

游乐场


我发现了jcalz的另一个答案,它提供了一个比我的更优雅的解决方案。请检查答案以获得解释。我将把它附加在这里:

type Remap2<
M extends { [k: string]: string },
O extends { [P in keyof M]: any }
> = {
[P in M[keyof M]]: O[
{ [K in keyof M]: M[K] extends P ? K : never }[keyof M]
]
};

这个版本怎么样:

type Remap<
M extends { [k: string]: string },
O extends { [k: string]: any }
> = {
[P in keyof M  as M[P]]: P extends keyof O ? O[P] : never
} & Omit<O, keyof M>

游乐场

最新更新