Reputation: 3320
I have the following type definitions as helpers:
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]]
}
With that, I can generate a type from a simple object. For example:
const TestModel1 = {
id: "number",
lastname: "string",
firstname: "string",
valid: "boolean"
} as const;
type TestRecord1 = MapSchema<typeof TestModel1>;
Result is correct:
type TestRecord1 = {
id: number;
lastname: string;
firstname: string;
valid: boolean;
}
But now, I would like to generate the exact same type but from a more complex object:
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>;
Errors:
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)
It obviously generates the wrong type:
type TestRecord2 = {
id: unknown;
lastname: unknown;
firstname: unknown;
valid: unknown;
}
And I cannot figure how to do it correctly. I know that I have to change the MapSchema type but everything I tried failed so far.
For example I tried:
type MapSchema<T extends Record<string, keyof MapSchemaTypes>> = {
-readonly [K in keyof T]: MapSchemaTypes[T[K]["type"]]
}
But it gives me the following error:
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)
Or again:
type MapSchema<T extends Record<string, keyof MapSchemaTypes>> = {
-readonly [K in keyof T]: MapSchemaTypes[T[K].type]]
}
Error:
Cannot find name 'type'.ts(2304)
Is it even possible to achieve what I want to do?
Following @Titian Cernicova-Dragomir very helpful answer, here are my updated type helpers:
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]
};
This lets me do the following:
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>;
And the resulting type is correct:
type TestRecord4 = {
id: number;
lastname: string;
firstname: string;
valid: boolean;
}
Upvotes: 1
Views: 382
Reputation: 250056
The constraint you have on T
is wrong if you want the properties of T
to be an object with a type property, you should use Record<string, { type: keyof MapSchemaTypes }>
type MapSchema<T extends Record<string, { type: keyof MapSchemaTypes }>> = {
-readonly [K in keyof T]: MapSchemaTypes[T[K]["type"]]
}
If you want it to work for both complex objects and just the type name, you can use a union in the constraint and a conditional type to discriminate between the two casses:
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]
}
Upvotes: 1