提问人:Beeno Tung 提问时间:10/29/2023 更新时间:10/30/2023 访问量:92
如何在Typescript中使用类型推断转换对象字段名称?
How to transform object field name using type infer in Typescript?
问:
我已经实现了一个具有转换对象键的解析器构建器(它在 js 中工作),但难以转换推断类型的对象键。
示例代码:
let sampleData = {
id: 1,
delete_time$optional: new Date()
}
let parser = inferFromSampleValue(sampleData)
构造的解析器在输入对象中需要一个数字和一个可选的 Date。id
delete_time
在我的初始实现中:
type Parser<T> = {
// throw runtime error if type not matched
parse(input: unknown): T
// typescript type of expected data in string
type: string
}
function inferFromSampleValue<T>(value: T): Parser<T> {
// implementation omitted
}
解析器结果的类型签名为:
{
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>> {
// implementation omitted
}
然后,解析器结果的类型签名变为:
{
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
上面发布的示例代码是简化版本,实际代码涉及更多类型的转换(对于可为 null 的字段和枚举)并以递归方式应用。
这是针对在 github 和 npm 上发布的开源项目“cast.ts”。链接到相关行: https://github.com/beenotung/cast.ts/blob/644e5bf/src/core.ts#L1067-L1154
谢谢你的帮助。
答:
0赞
jcalz
10/30/2023
#1
一种方法是给出以下调用签名:inferFromSampleValue()
declare function inferFromSampleValue<T>(value: T): Parser<
{ [K in keyof T as K extends `${string}\$optional` ? never : K]: T[K] } &
{ [K in keyof T as K extends `${infer P}\$optional` ? P : never]?: T[K] }
>;
这将使用映射类型中的键重新映射来筛选以 . 结尾或不以 . 结尾的键在第一种情况下,它会过滤掉这些键,因此您最终只能获得非可选属性。在第二种情况下,它会过滤这些键,但会删除后缀。此外,第二种情况使用映射修饰符,以便生成的类型具有所有可选属性。这两种情况相交在一起,以创建具有非可选属性和可选属性的单个对象类型的等效项,如预期所示:"$optional"
"$optional"
?
let sampleData = {
id: 1,
delete_time$optional: new Date()
}
let parser = inferFromSampleValue(sampleData);
declare let input: unknown
const v = parser.parse(input);
// ^? const v: { id: number; } & { delete_time?: Date | undefined; }
v.id.toFixed(1); // okay
v.delete_time?.getFullYear(); // okay
看起来不错!
评论
$
$