如何创建匹配输入结构的通用动态类型?

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
//     }
// }
//