【问题标题】:How to create generic dynamic types matching input structure?如何创建匹配输入结构的通用动态类型?
【发布时间】:2020-06-11 08:44:08
【问题描述】:

我正在构建一个对象模式验证函数,并且我正在尝试使返回类型动态匹配输入参数的结构。

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

主要目标是让 IDE 执行正确的自动完成,具体取决于输入的结构(并避免 {[key:string]: unknown}):

我尝试了以下类型的转换器,但它没有按预期工作:

type Magic_Type<T extends Schema> = {
    [P in keyof T]: typeof T[P]['default']
}

感谢您的宝贵时间!

【问题讨论】:

    标签: typescript dynamic types casting transformation


    【解决方案1】:

    有多种因素会阻止您获得正确的结果。首先,当您执行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
    //     }
    // }
    //
    
    

    【讨论】:

      猜你喜欢
      • 2018-12-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-03
      • 1970-01-01
      • 1970-01-01
      • 2014-06-09
      • 1970-01-01
      相关资源
      最近更新 更多