【问题标题】:How to create a custom type from some properties of a complex object?如何从复杂对象的某些属性中创建自定义类型?
【发布时间】:2021-02-10 15:12:50
【问题描述】:

我有以下类型定义作为助手:

type MapSchemaTypes = {
    string: string;
    number: number;
    integer: number;
    float: number;
    boolean: boolean;
};

type MapSchema<T extends Record<string, keyof MapSchemaTypes>> = {
    -readonly [K in keyof T]: MapSchemaTypes[T[K]]
}

有了它,我可以从一个简单的对象生成一个类型。例如:

const TestModel1 = {
    id: "number",
    lastname: "string",
    firstname: "string",
    valid: "boolean"
} as const;
type TestRecord1 = MapSchema<typeof TestModel1>;

结果正确:

type TestRecord1 = {
    id: number;
    lastname: string;
    firstname: string;
    valid: boolean;
}

但现在,我想从更复杂的对象生成完全相同的类型:

const TestModel2 = {
    id: {
        type: "number",
        mapping: "_id",
        defaultValue: 0
    },
    lastname: {
        type: "string",
        mapping: "_lastname",
        defaultValue: ""
    },
    firstname: {
        type: "string",
        mapping: "_firstname",
        defaultValue: ""
    },
    valid: {
        type: "boolean",
        mapping: "_valid",
        defaultValue: false
    }
} as const;
type TestRecord2 = MapSchema<typeof TestModel2>;

错误:

Type '{ readonly id: { readonly type: "number"; readonly mapping: "_id"; readonly defaultValue: 0; }; readonly lastname: { readonly type: "string"; readonly mapping: "_lastname"; readonly defaultValue: ""; }; readonly firstname: { ...; }; readonly valid: { ...; }; }' does not satisfy the constraint 'Record<string, "string" | "number" | "boolean" | "integer" | "float">'.
Property 'id' is incompatible with index signature.
    Type '{ readonly type: "number"; readonly mapping: "_id"; readonly defaultValue: 0; }' is not assignable to type '"string" | "number" | "boolean" | "integer" | "float"'.
    Type '{ readonly type: "number"; readonly mapping: "_id"; readonly defaultValue: 0; }' is not assignable to type '"float"'.ts(2344)

显然生成了错误的类型:

type TestRecord2 = {
    id: unknown;
    lastname: unknown;
    firstname: unknown;
    valid: unknown;
}

我不知道如何正确地做到这一点。我知道我必须更改 MapSchema 类型,但到目前为止我尝试的一切都失败了。

例如我试过:

type MapSchema<T extends Record<string, keyof MapSchemaTypes>> = {
    -readonly [K in keyof T]: MapSchemaTypes[T[K]["type"]]
}

但它给了我以下错误:

Type 'T[K]["type"]' cannot be used to index type 'MapSchemaTypes'.ts(2536)
Type '"type"' cannot be used to index type 'T[K]'.ts(2536)

或者再次:

type MapSchema<T extends Record<string, keyof MapSchemaTypes>> = {
    -readonly [K in keyof T]: MapSchemaTypes[T[K].type]]
}

错误:

Cannot find name 'type'.ts(2304)

是否有可能实现我想做的事情?

更新

在@Titian Cernicova-Dragomir very helpful answer 之后,这是我更新的类型助手:

type MapSchemaTypes = {
    string: string;
    number: number;
    integer: number;
    float: number;
    boolean: boolean;
};
type MapSchemaDefaultValues = string | number | boolean | undefined | null;
type MapSchemaSimpleField   = keyof MapSchemaTypes;
type MapSchemaComplexField  = {
    type: MapSchemaSimpleField;
    mapping: string;
    defaultValue: MapSchemaDefaultValues
};
type MapSchemaField = MapSchemaSimpleField | MapSchemaComplexField;
type MapSchemaDefinition = Record<string, MapSchemaField>;
type MapSchema<T extends MapSchemaDefinition> = {
    -readonly [K in keyof T]: T[K] extends { type: infer TypeName } ? MapSchemaTypes[TypeName & MapSchemaSimpleField] : MapSchemaTypes[T[K] & MapSchemaSimpleField]
};

这让我可以做到以下几点:

const TestModel4 = {
    id: "number",
    lastname: {
        type: "string",
        mapping: "_lastname",
        defaultValue: ""
    },
    firstname: "string",
    valid: {
        type: "boolean",
        mapping: "_valid",
        defaultValue: false
    }
} as const;
type TestRecord4 = MapSchema<typeof TestModel4>;

而且结果类型是正确的:

type TestRecord4 = {
    id: number;
    lastname: string;
    firstname: string;
    valid: boolean;
}

【问题讨论】:

    标签: typescript types type-conversion


    【解决方案1】:

    如果您希望T 的属性成为具有类型属性的对象,您对T 的约束是错误的,您应该使用Record&lt;string, { type: keyof MapSchemaTypes }&gt;

    type MapSchema<T extends Record<string, { type: keyof MapSchemaTypes }>> = {
        -readonly [K in keyof T]: MapSchemaTypes[T[K]["type"]]
    }
    

    Playground Link

    如果您希望它同时适用于复杂对象和类型名称,您可以在约束中使用联合和条件类型来区分这两种情况:

    type MapSchema<T extends Record<string, { type: keyof MapSchemaTypes } | keyof MapSchemaTypes>> = {
        -readonly [K in keyof T]: T[K] extends { type: infer TypeName } ? MapSchemaTypes[TypeName &  keyof MapSchemaTypes] : MapSchemaTypes[T[K] & keyof MapSchemaTypes]
    }
    
    

    Playground Link

    【讨论】:

    • 哇,它有效!非常感谢!有没有办法让它与具有相同 MapSchema 类型的两个对象(TestModel1 和 TestModel2)一起工作?
    • @GabrielHautclocq 扩展答案以包含此场景
    • 非常感谢,你真是个天才!
    • 我知道我问了很多,但是生成的类型是否可以将“映射”的值(如果存在于对象中)作为类型键?例如,它会生成像type TestRecord3 = { id: number; _lastname: string; firstname: string; _valid: boolean; } 这样的类型
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多