TypeScript中TranslatedObject如何深度转换可选嵌套属性?
修复TypeScript TranslatedObject处理可选嵌套属性的问题
原类型功能
TranslatedObject类型用于TypeScript对象转换,通过条件类型推断和模板字面量类型移除带De、Fr、It、En语言后缀的属性,返回无后缀属性,并递归处理除IgnoredTypes外的对象类型。原定义如下:
/** * TranslatedObject works with conditional type inference and template literal types * It will remove properties with the language suffixes (De, Fr, It and En) and simply return the non-suffixed property * This is done recursively for all properties whose types extend from `object` excluding {@link IgnoredTypes} * * @example * * interface Car { * nameDe: string, * nameFr: string, * nameIt: string, * nameEn: string, * price: number * } * type TranslatedCar = TranslatedObject<Car>; * * Result: * type TranslatedCar = { * name: string; * price: number; * } */ export type TranslatedObject<T> = T extends readonly any[] ? { [I in keyof T]: TranslatedObject<T[I]> } : { [K in keyof T as K extends `${infer Name}${'De' | 'Fr' | 'It' | 'En'}` ? Name : K]: T[K] extends object ? (T[K] extends IgnoredTypes[number] ? T[K] : TranslatedObject<T[K]>) : T[K]; }; // IgnoredTypes is needed because `Date` for example extends `object` but Date won't have any translated properties type IgnoredTypes = [Date];
问题描述
当处理包含可选嵌套属性的对象时,类型转换失效。例如定义包含可选gimmicks属性的Car接口:
interface Car { nameDe: string, nameFr: string, nameIt: string, nameEn: string, price: number, gimmicks?: Gimmick[] } type Gimmick = { nameDe: string, nameEn: string } type TranslatedCar = TranslatedObject<Car>;
转换后的TranslatedCar类型中,gimmicks的嵌套类型未被正确转换,赋值时会报错:
Object literal may only specify known properties, and 'name' does not exist in type 'Gimmick'.(2353)
修复方案
修改TranslatedObject类型,增加对undefined类型的处理,并确保联合类型(如Gimmick[] | undefined)能被正确递归转换:
type IgnoredTypes = [Date]; export type TranslatedObject<T> = // 处理数组类型,递归转换每个元素 T extends readonly any[] ? { [I in keyof T]: TranslatedObject<T[I]> } : // 处理对象类型(排除忽略类型) T extends object ? ( T extends IgnoredTypes[number] ? T : { // 保留原属性的可选性,同时处理语言后缀 [K in keyof T as K extends `${infer Name}${'De' | 'Fr' | 'It' | 'En'}` ? Name : K]: // 分发联合类型,处理undefined情况 T[K] extends infer U ? U extends undefined ? U : U extends object ? (U extends IgnoredTypes[number] ? U : TranslatedObject<U>) : U : never; } ) : // 非对象类型直接返回 T;
修复说明
- 处理联合类型分发:通过
T[K] extends infer U触发条件类型的分布式特性,确保undefined和实际对象类型被分开处理; - 保留可选属性的undefined状态:当属性是可选类型时,明确保留
undefined分支,避免丢失可选性; - 递归转换嵌套类型:对于非
undefined的对象类型,继续递归应用TranslatedObject转换,确保嵌套的带后缀属性也被处理。
验证结果
使用修改后的类型,TranslatedCar会被正确推断为:
type TranslatedCar = { name: string; price: number; gimmicks?: { name: string }[] | undefined; }
此时赋值操作不再报错:
const car: TranslatedCar = { name: 'carname', price: 15, gimmicks: [{name: 'gimmick 1'}] }
内容的提问来源于stack exchange,提问作者Micha
相关产品推荐
相关产品推荐

