I have a code setup like this.
I want the returned data to have a dynamic type constraint.
type Schema = ('id' | 'name' | 'description' | 'content' | 'createdAt')[];
type Obj = {
id: number;
name: string;
description: string;
content: string;
createdAt: Date;
}
function getBySchema(schema: Schema) {
const result = /* Get the object of the specified field from somewhere according to the schema */
return result;
}
const picked = getBySchema(["id", "name"]);
// Expect it to pass.
picked.name
// Expect it not to pass.
picked.createdAt
I specify Schema in the argument type of the function to get an input prompt.
How do I dynamically calculate the type of result based on the actual value of the schema?
I have tried the following, but it seems that it makes judgments based on all the types in the schema.
type PickBySchema<T extends Record<string, unknown>, S extends Schema> = S extends (infer I)[] ? I extends string ? {
[K in I]: T[K]
} : never : never;
CodePudding user response:
You can use combination of ReadonlyArray
and <infer U> ? U : never
to infer the passed down tuple of keys as follows:
function getBySchema<T extends Schema>(schema: T): {
[key in (T extends ReadonlyArray<infer U> ? U : never)]: Obj[key]} {
Explanation:
T extends Schema
makes sure that the passed in argument forschema: T
is of typeSchema
.- To use the passed in schema entries as the returned object keys we will first need to create an interface using Mapped types where keys are derived from a union of the
Schema
strings that were passed in. For this we start the interface withkey in
as normal, and then instead of referencing theSchema
type or its keys, we extend the genericT
variable to extract what was passed in. We do this by first asserting that it is aReadonlyArray
to create a union and usinginfer U
we get the smallest possible union type that is valid for T (a.k.a what was passed in) and return that using? U
.: never
is used to indicate that if a union cannot be inferred, the key is not a valid value and cannot be created. - Once we have derived the union of possible keys, we make the corresponding value type as
Obj[key]
which ensures that the property value has the correct type respective to the key.
Playground link.
Further improvement to the codebase:
You don't necessarily have to manually type out the keys inside Obj
type and assign them to the Schema
type. You can automate this by using:
type Schema = (keyof Obj)[];
Playground link.