如何为含复杂对象结构的TypeScript函数调整泛型类型?
问题描述
现有如下TypeScript代码片段:
type Query = { model: string; on: string; fields: string[]; query?: Query[]; }; function getModel(config: { object: xmlrpc.Client db: string userId: number password: string }) { return <T extends { [key: string]: any }>( model: string, fields: Extract<keyof T, string>[], props?: { offset?: number limit?: number where?: [string, string, any][] query?: Query[] } ): Promise<(T & { id: number })[]> => new Promise((resolve, reject) => { // Implementation details }).then(async (res) => { // Post-processing logic }); }
调用getModel时,result的推断类型不符合预期,调用代码如下:
const config = { object, db, userId, password }; const result = await getModel(config)( "product.template", ["name", "categ_id", "attribute_line_ids", "service_type"], { limit: 5, where: [["categ_id", "=", 40]], query: [ { model: "product.template.attribute.line", on: "attribute_line_ids", fields: ["value_ids"], query: [ { model: "product.attribute.value", on: "value_ids", fields: ["name"], }, ], }, ], } );
期望result的类型为:
const result: ({ name: any categ_id: any attribute_line_ids: ({ value_ids: ({ name: any } & { id: number })[] } & { id: number })[] service_type: any } & { id: number })[]
如何调整泛型类型定义,确保推断类型与预期结构匹配?
解决方案
要让TypeScript正确推断嵌套关联的类型,需要让类型系统能递归识别查询的层级结构,具体调整如下:
1. 重构Query为递归泛型类型
将原有的Query类型改为带泛型参数的递归类型,明确字段和嵌套查询的类型关联:
type Query<Fields extends string = string, NestedQueries extends Query<any, any>[] = Query<any, any>[]> = { model: string; on: string; fields: Fields[]; query?: NestedQueries; };
2. 定义递归解析结果的工具类型
创建ResolveQueryResult工具类型,用来递归解析每个查询对应的返回结构:
type ResolveQueryResult<Q extends Query<any, any>> = Q extends Query<infer Fields, infer NestedQueries> ? (Record<Fields, any> & { id: number }) & { [K in NestedQueries[number]["on"]]: ResolveQueryResult<Extract<NestedQueries[number], { on: K }>>[] } : never;
这个类型会:
- 生成当前查询字段的键值对类型,同时附加必有的
id字段 - 遍历所有嵌套查询,将
on指定的字段映射为对应嵌套查询的结果数组
3. 调整getModel的泛型参数
修改getModel的返回函数,让它接收顶层字段和嵌套查询作为泛型参数,组合出最终的返回类型:
function getModel(config: { object: xmlrpc.Client db: string userId: number password: string }) { return <TopLevelFields extends string, NestedQueries extends Query<any, any>[] = []>( model: string, fields: TopLevelFields[], props?: { offset?: number limit?: number where?: [string, string, any][] query?: NestedQueries } ): Promise<( Record<TopLevelFields, any> & { id: number } & { [K in NestedQueries[number]["on"]]: ResolveQueryResult<Extract<NestedQueries[number], { on: K }>>[] } )[]> => new Promise((resolve, reject) => { // Implementation details }).then(async (res) => { // Post-processing logic }); }
验证效果
现在调用getModel时,TypeScript会自动推断出与你预期完全一致的result类型:
const result = await getModel(config)( "product.template", ["name", "categ_id", "attribute_line_ids", "service_type"], { limit: 5, where: [["categ_id", "=", 40]], query: [ { model: "product.template.attribute.line", on: "attribute_line_ids", fields: ["value_ids"], query: [ { model: "product.attribute.value", on: "value_ids", fields: ["name"], }, ], }, ], } );
此时result的推断类型为:
({ name: any; categ_id: any; attribute_line_ids: ({ value_ids: ({ name: any; } & { id: number; })[]; } & { id: number; })[]; service_type: any; } & { id: number; })[]
扩展说明
如果需要替换any为更具体的字段类型,可以给Query和ResolveQueryResult添加额外泛型参数传入具体类型定义,当前实现已满足结构推断的核心需求。
内容的提问来源于stack exchange,提问作者stu
相关产品推荐
相关产品推荐

