如何用TypeScript泛型推断数组转对象类型并校验长度与唯一键?
TypeScript泛型实现键值数组转对象(含长度与键唯一性约束)
核心需求回顾
我们需要实现一个类似_.zipObject的函数,将两个数组分别作为键和值生成对应类型的对象,同时满足:
- 自动推断对象的键值类型,优先支持位置对应精确类型
- 强制约束键数组与值数组长度一致
- 强制约束键数组中的键唯一
1. 实现精确的键值类型推断
要让TypeScript根据输入的键值元组,推断出每个键对应对应位置值类型的对象,我们可以利用元组索引映射实现:
// 核心转换类型:将键元组与值元组映射为对象类型 type ZipObject<K extends readonly string[], V extends readonly any[]> = { [I in keyof K as K[I] extends string ? K[I] : never]: V[I] };
这个类型的逻辑是:遍历键元组的每个索引I,将索引对应的键K[I]作为对象的属性名,将值元组对应索引的类型V[I]作为属性值类型,最终生成精确匹配的对象类型。
2. 约束键值数组长度一致
通过泛型约束让K和V的长度属性完全相等,就能强制要求输入的两个数组长度相同:
K extends readonly string[] // 约束长度一致:值数组长度必须等于键数组长度 V extends readonly any[] & { length: K['length'] }
这里利用TypeScript的元组长度字面量特性,只有当两个元组长度完全相同时,V['length']才会匹配K['length']。
3. 约束键数组无重复元素
我们需要自定义一个类型工具来检查元组中是否存在重复字符串,再通过泛型约束拒绝存在重复键的数组:
// 检查元组中是否存在重复键的类型工具 type DuplicateKeys<T extends readonly string[]> = T extends [infer First, ...infer Rest] ? Rest extends readonly string[] ? First extends Rest[number] ? First // 存在重复,返回重复的键 : DuplicateKeys<Rest> // 递归检查剩余元素 : never : never; // 无重复,返回never
然后在泛型约束中添加:DuplicateKeys<K> extends never,这样当键数组存在重复时,TypeScript会直接报错。
完整实现与测试
将以上逻辑整合到函数中,最终实现如下:
// 检查元组中是否存在重复键的类型工具 type DuplicateKeys<T extends readonly string[]> = T extends [infer First, ...infer Rest] ? Rest extends readonly string[] ? First extends Rest[number] ? First : DuplicateKeys<Rest> : never : never; // 核心键值转对象类型 type ZipObject<K extends readonly string[], V extends readonly any[]> = { [I in keyof K as K[I] extends string ? K[I] : never]: V[I] }; function objectBuilder< K extends readonly string[] & (DuplicateKeys<K> extends never ? K : never), V extends readonly any[] & { length: K['length'] } >(keyArr: K, valArr: V): ZipObject<K, V> { const result = {} as ZipObject<K, V>; keyArr.forEach((key, i) => { result[key] = valArr[i] as ZipObject<K, V>[typeof key]; }); return result; }
测试示例
// 合法:键值长度一致,键唯一,类型推断精确 const obj1 = objectBuilder(['foo', 'bar', 'baz'], [11, 22, 33]); // obj1 类型:{ foo: number; bar: number; baz: number; } // 合法:混合值类型,推断精确对应类型 const obj2 = objectBuilder(['foo', 'bar', 'baz'], [11, "22", false]); // obj2 类型:{ foo: number; bar: string; baz: boolean; } // 报错:值数组长度不足 objectBuilder(['foo', 'bar', 'baz'], [11, 22]); // 报错:值数组长度过长 objectBuilder(['foo', 'bar'], [11, 22, 33]); // 报错:键数组存在重复的'foo' objectBuilder(['foo', 'bar', 'foo'], [11, 22, 33]);
内容的提问来源于stack exchange,提问作者mafachu
相关产品推荐
相关产品推荐

