TypeScript类型层面:如何将对象类型转为名称/类型对列表?
对象类型与键值对元组的双向转换问题
正向转换:元组转对象(已实现)
把名称/类型对的元组列表转换为对象类型并不难,实现代码如下:
type PropertySpec<K extends string|number|symbol, V> = readonly [K, V]; type UnknownProp = PropertySpec<string|number|symbol, unknown>; type PropSet = ReadonlyArray<UnknownProp>; type CreateObject<Props extends PropSet, OutObj extends Record<string|number|symbol, unknown> = {}> = Props extends [] ? OutObj : Props extends readonly [infer Prop, ...infer Props] ? Prop extends readonly [infer K, infer V] ? Props extends PropSet ? K extends string|number|symbol ? CreateObject<Props, OutObj & Record<K, V>> : readonly [`Bad Key`, K] : "Bad props" : readonly ["Bad prop", Prop] : "Bad prop list"; type Q1 = CreateObject<readonly [readonly ["a", string], readonly ["b", number]]>; const q1a: Q1 = {a: "foo", b: 3}; // OK! const q1b: Q1 = {a: "foo"}; // 正确提示缺少b属性 const q1c: Q1 = {b: 3}; // 正确提示缺少a属性 const q1d: Q1 = {a: "foo", b: 3, c: null}; // 正确提示存在多余的c属性
反向转换的疑问
但反向转换(把对象类型转成名称/类型对的元组)却难住了我。这似乎得用到那种经典的、“不推荐使用”的联合类型转元组的变种方法——这类方法在相关讨论里出现过很多次。
我有两个问题:
- 这是不是实现对象转元组的唯一途径?
- 我觉得这种方法的弊端在这里可能不适用(毕竟元组的顺序对我来说无关紧要),这个想法对吗?
补充背景
我做这个是为了处理带对象映射的尾递归条件类型。元组可以逐个拆解处理,通过构建输出缓冲区实现尾递归(避开TypeScript的50层栈大小限制),但映射类型没法干净实现这种逻辑。把对象转成元组就能解决这个问题,之后再转回去就像上面的正向转换一样简单。
问题解答
核心结论
- 目前在TypeScript中,对象类型转键值对元组确实绕不开联合类型转元组的思路——因为对象的键值对本质上是一个联合类型,要把这个联合转成有序的元组结构,只能依赖联合类型的分发特性实现;
- 你的判断是对的:这类方法的核心弊端(比如元组顺序不可控、依赖TS内部实现细节)在你的场景里几乎没有影响。
具体实现方案
基于联合转元组的思路,这里给出适配需求的实现:
// 第一步:将对象类型转换为键值对的联合类型 type ObjectToPropUnion<T> = { [K in keyof T]: readonly [K, T[K]] }[keyof T]; // 第二步:将联合类型转换为元组类型 type UnionToTuple<U, Acc extends unknown[] = []> = U extends never ? Acc : UnionToTuple<Exclude<U, U extends infer X ? X : never>, [...Acc, U extends infer X ? X : never]>; // 最终:对象类型转键值对元组的入口类型 type ObjectToPropTuple<T> = UnionToTuple<ObjectToPropUnion<T>>; // 测试验证 type TestObj = {a: string, b: number}; type TestTuple = ObjectToPropTuple<TestObj>; // TestTuple 会是 readonly ["a", string] 和 readonly ["b", number] 组成的元组,顺序可能随机,但不影响后续转对象
方案适配性说明
- 尾递归友好:元组的拆解逻辑可以复用你之前
CreateObject的尾递归写法,轻松避开TS的栈大小限制; - 顺序无关:不管元组内键值对的顺序如何,最终转成对象类型时,交叉类型会自动合并成正确的结构;
- 稳定性:只要你的对象类型是常规的结构化类型(键为确定的字面量或已知类型),这个方案就能稳定工作,不会出现意外问题。
内容的提问来源于stack exchange,提问作者David Durschlag
相关产品推荐
相关产品推荐

