开启strictNullChecks时,带可选字段的类型安全泛型映射失效问题
strictNullChecks=true时不支持可选字段的问题 我用TypeScript实现了一个基于泛型ComplexMapping的映射服务,用来定义源类型(S)到目标类型(T)的字段映射规则,要求类型安全且支持嵌套结构。当前实现代码如下:
type FieldMapping<S> = { source: keyof S; }; // 用于定义包含对象元素的数组属性映射 type ArrayFieldMapping<S, T> = { baseSource: keyof S; itemMapping: ComplexMapping<S[keyof S] extends (infer U)[] ? U : never, T>; }; type IsConfidenceType<T> = T extends { confidence: number } ? true : false; // 映射定义类型 export type ComplexMapping<S, T> = { [K in keyof T]: T[K] extends Array<infer Item> ? Item extends object ? ArrayFieldMapping<S, Item> : never : T[K] extends object ? IsConfidenceType<T[K]> extends true ? FieldMapping<S> : { [P in keyof T[K]]: T[K][P] extends object ? ComplexMapping<S, T[K]>[P] : FieldMapping<S>; } : FieldMapping<S>; };
当我针对以下具体类型创建映射定义时,在strictNullChecks=true的配置下出现了类型错误,但关闭该配置或者移除目标类型的可选字段标记后就能正常工作。需求必须保留可选字段且不能关闭strictNullChecks。
type SourceType = { aSource: string; deliveryNotes: { name: string }[]; }; // 移除?标记后,在strictNullCheck=true时可正常工作 type TargetType = { aTarget: string; deliveryNotesTarget?: { lala: string }[]; }; // 类型安全的映射定义,无法选择不存在的属性 const mappingDefinition: ComplexMapping<SourceType, TargetType> = { aTarget: { source: "aSource", }, deliveryNotesTarget: { // 开启strictNullCheck时此处出现类型错误 baseSource: "deliveryNotes", itemMapping: { lala: { // 仅当使用as ComplexMapping时此处才有类型安全 source: "name", }, } as ComplexMapping< SourceType["deliveryNotes"][number], TargetType["deliveryNotesTarget"][number] >, }, };
开启strictNullChecks=true时,deliveryNotesTarget中的baseSource属性会报如下错误:
Object literal may only specify known properties, and 'baseSource' does not exist in type 'FieldMapping'.(2353)
问题分析
问题根源是可选字段在strictNullChecks开启时会被TypeScript解析为T[K] | undefined的联合类型,而原ComplexMapping的类型推导逻辑没有处理这种情况——它直接对T[K]做数组判断,当T[K]是Array<Item> | undefined时,T[K] extends Array<infer Item>会返回false,导致该字段被错误归类为普通FieldMapping<S>类型,而非预期的ArrayFieldMapping。
解决方案
修改ComplexMapping类型,先通过NonNullable<T[K]>提取可选字段的非undefined类型,再进行后续的数组和对象判断;同时修正ArrayFieldMapping中的源数组元素类型推导逻辑,确保准确获取源类型的数组元素类型。
修改后的完整代码:
type FieldMapping<S> = { source: keyof S; }; // 修正数组字段映射的源元素类型推导逻辑 type ArrayFieldMapping<S, TItem> = { baseSource: keyof S; itemMapping: ComplexMapping<Extract<S[keyof S], any[]>[number], TItem>; }; type IsConfidenceType<T> = T extends { confidence: number } ? true : false; // 修正ComplexMapping,处理可选字段的联合类型 export type ComplexMapping<S, T> = { [K in keyof T]: NonNullable<T[K]> extends Array<infer Item> ? Item extends object ? ArrayFieldMapping<S, Item> : never : NonNullable<T[K]> extends object ? IsConfidenceType<NonNullable<T[K]>> extends true ? FieldMapping<S> : ComplexMapping<S, NonNullable<T[K]>> : FieldMapping<S>; };
验证效果
使用修改后的类型,原映射定义可以在strictNullChecks=true下正常工作,且保留了所有类型安全特性:
type SourceType = { aSource: string; deliveryNotes: { name: string }[]; }; type TargetType = { aTarget: string; deliveryNotesTarget?: { lala: string }[]; }; const mappingDefinition: ComplexMapping<SourceType, TargetType> = { aTarget: { source: "aSource", }, deliveryNotesTarget: { baseSource: "deliveryNotes", itemMapping: { lala: { source: "name", }, }, }, };
内容的提问来源于stack exchange,提问作者Andy

