TypeScript嵌套映射问题:预期对象类型却推断为never
问题:嵌套对象类型被推断为
never而非预期类型 开发TypeScript映射函数时,遇到嵌套对象类型推断错误的问题:处理后的processedResult.building类型被推断为never,但预期应为Building类型。函数支持简单字段映射、嵌套对象映射及验证逻辑,问题出在MapResult类型定义的嵌套推断逻辑上。
以下是简化后的代码:
type SimpleFieldOptions = { inputKey?: string; validators?: Array<(value: any) => boolean>; }; type NestedFieldOptions<M> = { nested: FieldMapping<M>; }; type FieldMapping<M> = { [K in keyof M]: SimpleFieldOptions | NestedFieldOptions<M[K]> | true; }; type DataMapping<M> = { fields: FieldMapping<M>; }; type MapResult<M, F extends DataMapping<M>> = { [K in keyof F['fields']]: F['fields'][K] extends SimpleFieldOptions ? M[K] : F['fields'][K] extends NestedFieldOptions<infer N> ? MapResult<N, DataMapping<N>> : F['fields'][K] extends true ? M[K] : never; }; function mapData<M, F extends DataMapping<M>>(payload: Record<string, any>, mapping: F): MapResult<M, F> { const result = {} as MapResult<M, F>; for (const key in mapping.fields) { const fieldConfig = mapping.fields[key]; const typedKey = key as keyof MapResult<M, F>; if (fieldConfig === true) { result[typedKey] = payload[key] as M[typeof typedKey]; } else if (isSimpleFieldOptions(fieldConfig)) { const value = payload[fieldConfig.inputKey || key]; if (fieldConfig.validators) { for (const validator of fieldConfig.validators) { if (!validator(value)) { throw new Error(`Validation failed for key: ${String(typedKey)} with value: ${value}`); } } } result[typedKey] = value as M[typeof typedKey]; } else if (isNestedFieldOptions(fieldConfig)) { result[typedKey] = mapData(payload, { fields: fieldConfig.nested }) as any; } } return result; } function isSimpleFieldOptions(option: any): option is SimpleFieldOptions { return option && typeof option === 'object' && ('inputKey' in option || 'validators' in option); } function isNestedFieldOptions<T>(option: any): option is NestedFieldOptions<T> { return option && typeof option === 'object' && option.nested && typeof option.nested === 'object'; } class Address { street!: string; city!: string; zipCode!: string; } class Building { name!: string; description!: string; address!: Address; } class Apartment { apartmentId!: string; buildingId!: string; building!: Building; } const apartmentMapping: DataMapping<Apartment> = { fields: { apartmentId: true, buildingId: true, building: { nested: { name: { inputKey: 'buildingName', validators: [(value) => typeof value === 'string'] }, description: { inputKey: 'buildingDescription', validators: [(value) => typeof value === 'string'] }, address: { nested: { street: { inputKey: 'streetName', validators: [(value) => typeof value === 'string'] }, city: { inputKey: 'cityName', validators: [(value) => typeof value === 'string'] }, zipCode: true, }, }, }, }, }, }; const payload = { apartmentId: '321-cba', buildingId: '123-abc', buildingName: 'Sunrise Apartments', buildingDescription: 'A modern apartment complex.', streetName: 'Sunset Boulevard', cityName: 'Los Angeles', zipCode: '90001', }; const processedResult = mapData(payload, apartmentMapping); // 预期 processedResult.building 类型为 Building,但实际为 never
解决方案
问题核心在于MapResult类型的嵌套推断逻辑错误:原定义中通过infer N提取的是FieldMapping类型而非目标实体类型(如Building),导致递归推断时类型失配。
步骤1:修正MapResult类型定义
调整嵌套字段的类型推断逻辑,直接从原始目标类型M中获取对应嵌套类型,而非从映射配置中推断:
type MapResult<M, F extends DataMapping<M>> = { [K in keyof F['fields']]: // 简单字段或直接映射,返回M[K]类型 F['fields'][K] extends SimpleFieldOptions | true ? M[K] : // 嵌套字段,递归处理M[K]对应的映射配置 F['fields'][K] extends NestedFieldOptions<any> ? MapResult<M[K], { fields: F['fields'][K]['nested'] }> : never; };
步骤2:优化函数内的类型断言
在嵌套调用mapData时,添加更精确的类型断言,避免使用any:
// 替换原嵌套分支的代码 else if (isNestedFieldOptions(fieldConfig)) { result[typedKey] = mapData(payload, { fields: fieldConfig.nested }) as MapResult<M[typeof typedKey], { fields: typeof fieldConfig.nested }>; }
修正后效果
修改后,processedResult.building的类型将正确推断为Building,其嵌套的address字段也会正确推断为Address类型,完全符合预期。
内容的提问来源于stack exchange,提问作者Patrick
相关产品推荐
相关产品推荐

