如何基于递归对象结构用TypeScript推导嵌套类型?
基于递归Item对象推导嵌套TypeScript类型的解决方案
问题背景
给定如下递归定义的TypeScript对象结构:
interface Item { name: string; type: 'string' | 'url' | 'list' | 'file', require?: boolean; allowedFileTypes: string[]; subFields: Item[]; } const items = { name: "navigation", type: "list", subFields: [ { name: "label", type: "string", defaultValue: "", require: true, }, { name: "url", type: "url", defaultValue: "", require: true, }, { name: "images", type: "list", subFields: [ { name: "label", type: "string", defaultValue: "", require: true, }, { name: "icon", type: "file", allowedFileTypes: ["png", "svg"], }, ], }, ], };
需要从该结构推导得到如下嵌套类型:
// 预期输出: type Items = { navigation: Array<{ label: string; url: string; images: Array<{ label: string; icon: 'png' | 'svg'; }> }> }
此前已完成扁平结构的推导,但递归嵌套部分遇到瓶颈,需解决:
- 利用
typeof结合泛型递归推导嵌套类型 - 根据
require标记处理可选属性 - 将
file类型的allowedFileTypes转换为字符串联合类型 - 确认该需求是否在TypeScript能力范围内
解决方案
该需求完全在TypeScript的能力范围内,可通过递归条件类型结合typeof实现。核心思路是针对不同type字段做分支处理,递归解析subFields,并根据require控制属性可选性。
实现代码
// 给items添加as const断言,保留字面量类型以便精确推导 const items = { name: "navigation", type: "list", subFields: [ { name: "label", type: "string", defaultValue: "", require: true, }, { name: "url", type: "url", defaultValue: "", require: true, }, { name: "images", type: "list", subFields: [ { name: "label", type: "string", defaultValue: "", require: true, }, { name: "icon", type: "file", allowedFileTypes: ["png", "svg"], }, ], }, ], } as const; // 递归类型转换器:将Item结构映射为目标类型 type ItemToType<T extends typeof items | typeof items['subFields'][number]> = // 处理string/url类型,直接映射为string T extends { type: 'string' | 'url' } ? string // 处理file类型,提取allowedFileTypes的字面量联合类型 : T extends { type: 'file'; allowedFileTypes: readonly string[] } ? T['allowedFileTypes'][number] // 处理list类型,递归生成数组对象结构 : T extends { type: 'list'; subFields: readonly any[] } ? Array< // 先处理必填属性 { [K in T['subFields'][number]['name'] as Extract<T['subFields'][number], { name: K, require: true }>['name']]: ItemToType<Extract<T['subFields'][number], { name: K }>> } & // 再处理可选属性 Partial<{ [K in T['subFields'][number]['name'] as Extract<T['subFields'][number], { name: K, require?: false }>['name']]: ItemToType<Extract<T['subFields'][number], { name: K }>> }> > : never; // 生成最终目标类型 type Items = { [K in typeof items['name']]: ItemToType<typeof items> };
关键说明
as const断言:强制TypeScript保留对象中字符串、数组的字面量类型(比如将["png", "svg"]识别为readonly ["png", "svg"],而非string[]),这是生成精确联合类型的前提。- 递归条件分支:
- 对
string/url类型直接映射为string; - 对
file类型提取allowedFileTypes的元素类型作为联合类型; - 对
list类型递归解析subFields,生成对应的数组嵌套结构;
- 对
- 可选属性处理:通过
Extract分别筛选出require: true的必填字段和require为false或未定义的可选字段,再通过Partial标记可选性; - 最终类型生成:以根节点的
name为键,调用递归类型转换器得到完整嵌套类型。
验证结果
上述代码会生成完全符合预期的类型:
type Items = { navigation: Array<{ label: string; url: string; images: Array<{ label: string; icon: "png" | "svg"; }>; }>; };
内容的提问来源于stack exchange,提问作者pilotguy
相关产品推荐
相关产品推荐

