Home > Back-end >  Construct TypeScript type from array of objects
Construct TypeScript type from array of objects

Time:08-18

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

CodePudding user response:

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

CodePudding user response:

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>;
  • Related