TypeScript中如何将元组对象映射为对象类型?
TypeScript类型转换问题解决:Config转Instance
问题背景
定义了如下Config类型:
type Config = { endpoints: [{name: "abc", created: false}, {name: "xyz", created: true}] }
需要转换为目标Instance类型:
type Instance = { abc: {name: "abc", created: false}, xyz: {name: "xyz", created: true} }
原编写的Transform类型出现**"name无法作为T['endpoints'][K[key]]的索引"**错误;添加数字类型守卫后错误消失,但Instance类型变为unknown。
原错误代码:
type IntersectOf<U extends any> = (U extends unknown ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never; type Transform<T extends Config, K extends (keyof T)[] = (keyof T)[]> = IntersectOf<{ [key in keyof K]: K[key] extends keyof T['endpoints'] ? Record<T['endpoints'][K[key]]['name'], T['endpoints'][K[key]]> : never; }[number]>; type Instance = Transform<Config>;
添加类型守卫后的代码(结果为unknown):
type Trasnform<T extends Config, K extends (keyof T)[] = (keyof T)[]> = IntersectOf<{ [key in keyof K]: K[key] extends keyof T['endpoints'] ? K[key] extends number ? Record<T['endpoints'][K[key]]['name'], T['endpoints'][K[key]]> : never : never; }[number]>;
错误原因分析
- 原代码中
K extends (keyof T)[]约束错误:keyof T是"endpoints",所以K被限定为["endpoints"]数组,和遍历endpoints数组索引的需求完全不匹配,导致后续类型推导失败。 - 添加数字守卫后,由于
K的类型是["endpoints"],K[key]永远不会是number,所有分支返回never,最终IntersectOf处理never得到unknown。
正确实现方案
直接利用TypeScript的重映射类型,遍历endpoints数组的元素类型,将元素的name作为键,元素本身作为值:
type Config = { endpoints: [{name: "abc", created: false}, {name: "xyz", created: true}] } type Transform<T extends { endpoints: readonly { name: string }[] }> = { [E in T['endpoints'][number] as E['name']]: E; }; type Instance = Transform<Config>; // 最终结果:{ abc: { name: "abc"; created: false; }, xyz: { name: "xyz"; created: true; } }
方案说明
T['endpoints'][number]提取endpoints数组所有元素的联合类型:{name: "abc", created: false} | {name: "xyz", created: true}as E['name']是重映射语法,将联合类型中每个元素的name属性作为新对象的键- 直接将元素
E作为对应键的值,一步得到目标类型
替代实现(保留IntersectOf思路)
如果想沿用原有的交叉合并思路,需要修正K的约束逻辑,正确提取数组元素类型:
type IntersectOf<U> = (U extends unknown ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never; type Transform<T extends { endpoints: readonly any[] }> = IntersectOf< T['endpoints'] extends readonly (infer E)[] ? E extends { name: infer N extends string } ? Record<N, E> : never : never >; type Instance = Transform<Config>; // 结果同样符合预期
修正说明
- 将
T约束为包含endpoints数组的类型,通过infer E提取数组元素类型E - 约束
E必须包含字符串类型的name属性,生成对应的Record类型 - 用
IntersectOf将所有Record类型交叉合并,得到最终的对象类型
内容的提问来源于stack exchange,提问作者Nauman Umer
相关产品推荐
相关产品推荐

