从对象数组构造 TypeScript 类型

dav*_*ler 5 typescript

我有数据库表的以下架构列(用于西瓜数据库)。

const columns = [
  { name: "created_at", type: "number", isOptional: true },
  { name: "created_by", type: "string" },
  { name: "is_corrupt", type: "boolean", isOptional: true },
];
Run Code Online (Sandbox Code Playgroud)

我想创建一个泛型,它将根据上面的示例创建以下类型,我该怎么做?

type ExpectedInferredTypeFromColumns = {
  created_at: number | null;
  created_by: string;
  is_corrupt: boolean | null;
};
Run Code Online (Sandbox Code Playgroud)

我的尝试:

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;
// }
Run Code Online (Sandbox Code Playgroud)

正如你所看到的,我的尝试不太符合我的要求ExpectedInferredTypeFromColumns

noo*_*ook 2

就这样:游乐场

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;
}
*/
Run Code Online (Sandbox Code Playgroud)

如果您需要拥有可选键,则需要将可选键和必需键进行交集,如下所示

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;
}
*/
Run Code Online (Sandbox Code Playgroud)

这个答案的灵感来自于这个解决方案:Conditionally apply ? 每个属性的映射类型中的修饰符