如何创建匹配输入结构的通用动态类型?
How to create generic dynamic types matching input structure?
我正在构建一个对象模式验证函数,我正在尝试使 return 类型动态匹配输入参数的结构。
// ----- Types
interface SchemaString {
type?: 'string',
required?: boolean,
default?: string
}
interface SchemaNumber {
type?: 'number',
required?: boolean,
default?: number
}
interface SchemaObject {
type?: 'object',
required?: boolean,
default?: {}
children?: Schema
}
type Schema = {[key:string]: SchemaString | SchemaNumber | SchemaObject};
// ----- Example
// Stuff happens here
function check<T extends Schema>(schema: T): Magic_Type<T>{
// Processing
}
const schema: Schema = {
foo: {
type: 'string'
}
bar: {
type: 'object',
children: {
baz: {type: 'string'}
}
}
};
const result = check(schema);
// 'result' type should be:
//
// {
// foo: string
// bar: {
// baz: string
// }
// }
//
主要 objective 是让 IDE 执行适当的自动完成,这取决于输入的结构(并避免 {[key:string]: unknown}
):
我尝试了以下类型的转换器,但它没有按预期工作:
type Magic_Type<T extends Schema> = {
[P in keyof T]: typeof T[P]['default']
}
感谢您的宝贵时间!
有多种因素阻止您获得正确的结果。首先,当您执行 const schema: Schema = { ... }
时,您实际上丢失了有关文字字符串的类型信息。解决方案是删除 : Schema
注释(因为它会丢失类型信息)并使用 as const
作为文字,或者使用一个特殊的身份函数来捕获文字类型。
接下来你必须让type
属性在你的架构上是强制性的,否则所有属性都是可选的,使any类型匹配架构,所以你不能做映射。
最后 "default"
属性 没有正确的对象类型,因此您需要改用条件类型。把它们放在一起你有这个:
// ----- Types
interface SchemaString {
type: 'string',
required?: boolean,
default?: string
}
interface SchemaNumber {
type: 'number',
required?: boolean,
default?: number
}
interface SchemaObject {
type: 'object',
required?: boolean,
default?: {}
children?: Schema
}
type SchemaItem = SchemaString | SchemaNumber | SchemaObject;
type Schema = {[key:string]: SchemaItem };
type SchemaObjectChildrenToType<T extends Schema | undefined> =
T extends Schema ? SchemaToType<T> : {};
type SchemaItemToType<T extends SchemaItem> =
T extends SchemaString ? string :
T extends SchemaNumber ? number :
T extends SchemaObject ? SchemaObjectChildrenToType<T["children"]> :
never
type SchemaToType<T extends Schema> =
{ [P in keyof T]: SchemaItemToType<T[P]> };
// Stuff happens here
function check<T extends Schema>(schema: T): SchemaToType<T>{
// Processing
return null as any;
}
function makeSchema<T extends Schema>(schema: T): T {
return schema;
}
const schema = {
foo: {
type: 'string'
},
bar: {
type: 'object',
children: {
baz: { type: 'string' }
}
}
} as const;
// const schemaAlternative = makeSchema({
// foo: {
// type: 'string'
// },
// bar: {
// type: 'object',
// children: {
// baz: { type: 'string' }
// }
// }
// });
const result = check(schema);
result.bar.baz.endsWith("bar"); // works!
// 'result' type should be:
//
// {
// foo: string
// bar: {
// baz: string
// }
// }
//
我正在构建一个对象模式验证函数,我正在尝试使 return 类型动态匹配输入参数的结构。
// ----- Types
interface SchemaString {
type?: 'string',
required?: boolean,
default?: string
}
interface SchemaNumber {
type?: 'number',
required?: boolean,
default?: number
}
interface SchemaObject {
type?: 'object',
required?: boolean,
default?: {}
children?: Schema
}
type Schema = {[key:string]: SchemaString | SchemaNumber | SchemaObject};
// ----- Example
// Stuff happens here
function check<T extends Schema>(schema: T): Magic_Type<T>{
// Processing
}
const schema: Schema = {
foo: {
type: 'string'
}
bar: {
type: 'object',
children: {
baz: {type: 'string'}
}
}
};
const result = check(schema);
// 'result' type should be:
//
// {
// foo: string
// bar: {
// baz: string
// }
// }
//
主要 objective 是让 IDE 执行适当的自动完成,这取决于输入的结构(并避免 {[key:string]: unknown}
):
我尝试了以下类型的转换器,但它没有按预期工作:
type Magic_Type<T extends Schema> = {
[P in keyof T]: typeof T[P]['default']
}
感谢您的宝贵时间!
有多种因素阻止您获得正确的结果。首先,当您执行 const schema: Schema = { ... }
时,您实际上丢失了有关文字字符串的类型信息。解决方案是删除 : Schema
注释(因为它会丢失类型信息)并使用 as const
作为文字,或者使用一个特殊的身份函数来捕获文字类型。
接下来你必须让type
属性在你的架构上是强制性的,否则所有属性都是可选的,使any类型匹配架构,所以你不能做映射。
最后 "default"
属性 没有正确的对象类型,因此您需要改用条件类型。把它们放在一起你有这个:
// ----- Types
interface SchemaString {
type: 'string',
required?: boolean,
default?: string
}
interface SchemaNumber {
type: 'number',
required?: boolean,
default?: number
}
interface SchemaObject {
type: 'object',
required?: boolean,
default?: {}
children?: Schema
}
type SchemaItem = SchemaString | SchemaNumber | SchemaObject;
type Schema = {[key:string]: SchemaItem };
type SchemaObjectChildrenToType<T extends Schema | undefined> =
T extends Schema ? SchemaToType<T> : {};
type SchemaItemToType<T extends SchemaItem> =
T extends SchemaString ? string :
T extends SchemaNumber ? number :
T extends SchemaObject ? SchemaObjectChildrenToType<T["children"]> :
never
type SchemaToType<T extends Schema> =
{ [P in keyof T]: SchemaItemToType<T[P]> };
// Stuff happens here
function check<T extends Schema>(schema: T): SchemaToType<T>{
// Processing
return null as any;
}
function makeSchema<T extends Schema>(schema: T): T {
return schema;
}
const schema = {
foo: {
type: 'string'
},
bar: {
type: 'object',
children: {
baz: { type: 'string' }
}
}
} as const;
// const schemaAlternative = makeSchema({
// foo: {
// type: 'string'
// },
// bar: {
// type: 'object',
// children: {
// baz: { type: 'string' }
// }
// }
// });
const result = check(schema);
result.bar.baz.endsWith("bar"); // works!
// 'result' type should be:
//
// {
// foo: string
// bar: {
// baz: string
// }
// }
//