如何在TypeScript中通过类型推断转换对象字段名?
解决TypeScript中解析器生成器的对象键类型转换问题
我实现了一个能转换对象键的解析器生成器(JavaScript中运行正常),但在TypeScript里无法正确推断转换后的对象键类型。
问题场景
示例代码:
let sampleData = { id: 1, delete_time$optional: new Date() } let parser = inferFromSampleValue(sampleData)
期望构造出的解析器要求输入对象包含必填数字类型id和可选日期类型delete_time。
初始实现的问题
初始类型定义:
type Parser<T> = { // 类型不匹配时抛出运行时错误 parse(input: unknown): T // 预期数据的TypeScript类型字符串描述 type: string } function inferFromSampleValue<T>(value: T): Parser<T> { // 实现细节省略 }
此时解析器的类型签名为:
{ id: number delete_time$optional: Date }
与预期的类型签名不符:
{ id: number delete_time?: Date }
尝试过的方案及问题
我尝试编写了以下泛型类型,但结果不符合预期:
type InferFieldName<S> = S extends `${infer N}$optional` ? N : S type ExcludeOptionalFieldName<S> = S extends `${infer N}$optional` ? never : S type InferType<T> = T extends Record<infer K extends keyof T, infer V> ? Partial<Record<InferFieldName<K>, V>> & Pick<T, ExcludeOptionalFieldName<keyof T>> : T function inferFromSampleValue<T>(value: T): Parser<InferType<T>> { // 实现细节省略 }
此时解析器的类型签名变成了:
{ id: number delete_time?: number | Date }
可选字段的类型被错误混合成number | Date,而非预期的Date。
我还尝试过这种写法,但TypeScript不支持该语法:
type InferType<T> = T extends {} ? { [InferFieldName<P> where P in keyof T]: T[P] } : T
注:实际代码包含更多类型转换(如可空字段、枚举)且支持递归处理,上述为简化版本。
解决方案
要正确实现键转换和类型保留,需使用映射类型结合条件类型分别处理必填和可选字段,避免类型交叉导致的混合问题。
正确的泛型类型定义
type InferFieldName<S extends string> = S extends `${infer N}$optional` ? N : S // 提取所有带$optional后缀的键 type OptionalKeys<T> = { [K in keyof T]: K extends `${string}$optional` ? K : never }[keyof T] // 提取所有不带$optional后缀的键 type RequiredKeys<T> = Exclude<keyof T, OptionalKeys<T>> type InferType<T> = // 处理必填字段:直接保留键和类型 { [K in RequiredKeys<T>]: T[K] } & // 处理可选字段:移除$optional后缀并设为可选 { [K in OptionalKeys<T> as InferFieldName<K>]?: T[K] } extends infer O // 将交叉类型展开为普通对象类型,提升可读性 ? { [P in keyof O]: O[P] } : never type Parser<T> = { parse(input: unknown): T type: string } function inferFromSampleValue<T>(value: T): Parser<InferType<T>> { // 实现细节省略 }
测试效果
let sampleData = { id: 1, delete_time$optional: new Date() } let parser = inferFromSampleValue(sampleData) // parser的类型为 Parser<{ id: number; delete_time?: Date | undefined }>
递归支持(针对嵌套对象)
如果需要处理嵌套对象中的$optional字段,可以修改InferType为递归类型:
type InferType<T> = T extends object ? { [K in keyof T]: InferType<T[K]> } extends infer Obj ? { [K in RequiredKeys<Obj>]: Obj[K] } & { [K in OptionalKeys<Obj> as InferFieldName<K>]?: Obj[K] } extends infer Final ? { [P in keyof Final]: Final[P] } : never : never : T
这样就能正确处理嵌套结构中的键转换和类型推断了。
内容的提问来源于stack exchange,提问作者Beeno Tung
相关产品推荐
相关产品推荐

