5

I have the following schema columns for a database table (used for Watermelon DB).

const columns = [ { name: "created_at", type: "number", isOptional: true }, { name: "created_by", type: "string" }, { name: "is_corrupt", type: "boolean", isOptional: true }, ]; 

I would like to create a generic which will create the following type from the above example, how can I do that?

type ExpectedInferredTypeFromColumns = { created_at: number | null; created_by: string; is_corrupt: boolean | null; }; 

My attempt:

type InferTypeFromColumns<T extends ReadonlyArray<Column>> = { [K in T extends ReadonlyArray<infer U> ? U extends { name: string } ? U["name"] : never : never]: T extends ReadonlyArray<infer U> ? U extends { type: "number"; isOptional: true } ? number | null : U extends { type: "number" } ? number : U extends { type: "string"; isOptional: true } ? string | null : U extends { type: "string" } ? string : U extends { type: "boolean"; isOptional: true } ? boolean | null : U extends { type: "boolean" } ? boolean : never : never; }; type MyInferredType = InferTypeFromColumns<typeof columns>; // Produces: => // type MyInferredType = { // created_at: string | number | boolean | null; // created_by: string | number | boolean | null; // is_corrupt: string | number | boolean | null; // } 

As you can see my attempt doesn't quite meet my ExpectedInferredTypeFromColumns

    2 Answers 2

    2

    There you go: playground

    const columns = [ { name: "created_at", type: "number", isOptional: true }, { name: "created_by", type: "string" }, { name: "is_corrupt", type: "boolean", isOptional: true }, ] as const; // define as const so `columns[number]` gives precise type inference type Column = { name: string; type: "number" | "string" | "boolean" isOptional?: boolean } type TypeMapper = { boolean: boolean; string: string; number: number; } // You need to create a union depending if `isOptional` is defined or not type InferTypeFromColumns<T extends ReadonlyArray<Column>> = { [K in T[number] as K['name']]: TypeMapper[K['type']] | (K['isOptional'] extends true ? null : never) } type Test = InferTypeFromColumns<typeof columns> /* type Test = { created_at: number | null; created_by: string; is_corrupt: boolean | null; } */ 

    If you need to have the optional keys, you will need to do an intersection of the optional and required keys like this:

    type RequiredInferTypeFromColumns<T extends ReadonlyArray<Column>> = { [K in T[number] as K['isOptional'] extends true ? never : K['name']]: TypeMapper[K['type']] } type OptionalInferTypeFromColumns<T extends ReadonlyArray<Column>> = { [K in T[number] as K['isOptional'] extends true ? K['name'] : never]?: TypeMapper[K['type']] | null } type Intersection<A, B> = A & B extends infer U ? { [P in keyof U]: U[P] } : never; type Test = Intersection<RequiredInferTypeFromColumns<typeof columns>, OptionalInferTypeFromColumns<typeof columns>> /* type Test = { created_by: string; created_at?: number | null | undefined; is_corrupt?: boolean | null | undefined; } */ 

    This answer was inspired by this solution: Conditionally apply ? modifier in mapped type per-property

    2
    • Thanks, I am struggling with one additional step, perhaps you could give a hint on how to solve. How would you also make those keys optional? ie desired output type Test = { created_at?: number | null; created_by: string; is_corrupt?: boolean | null; } Thanks!CommentedAug 18, 2022 at 18:52
    • 1
      See the edit :)
      – nook
      CommentedAug 19, 2022 at 8:41
    1

    Okay so by basically copying this answer I was able to get a working solution

    const columns = [ { name: "created_at", type: "number", isOptional: true }, { name: "created_by", type: "string", isOptional: false }, { name: "is_corrupt", type: "boolean", isOptional: true }, ] as const; type InferTypeFromColumns<P extends readonly unknown[]> = { [K in IndexKeys<P> as Name<P[K]>]: Type<P[K]>; }; type IndexKeys<A extends readonly unknown[]> = Exclude<keyof A, keyof []>; type Name<O> = O extends { name: infer N } ? N extends string ? N : never : never; type Type<T> = T extends { type: "number"; isOptional: true } ? number | null : T extends { type: "number" } ? number : T extends { type: "string"; isOptional: true } ? string | null : T extends { type: "string" } ? string : T extends { type: "boolean"; isOptional: true } ? boolean | null : T extends { type: "boolean" } ? boolean : never; type MyInferredType = InferTypeFromColumns<typeof columns>; 

      Start asking to get answers

      Find the answer to your question by asking.

      Ask question

      Explore related questions

      See similar questions with these tags.