首页 > 解决方案 > 将数组映射到具有正确类型的对象(仅类型方式)

问题描述

介绍

在我们的项目中,我们有属性支持,其中每个属性都是类。该属性包含有关类型、可选性和名称的信息。我不想为每个实体定义一个接口,而是让它自动化。我们有大约 500 个属性和 100 多个实体。实体是属性的收集器。

例子

interface AttributeClass {
  readonly attrName: string;
  readonly required?: boolean;
  readonly valueType: StringConstructor | NumberConstructor  | BooleanConstructor;
}

class AttrTest extends Attribute {
  static readonly attrName = "test";
  static readonly required = true;
  static readonly valueType = String
}

class Attr2Test extends Attribute {
  static readonly attrName = "test2";
  static readonly valueType = Number
}

interface Entity {
  test: string // AttrTest
  test2?: number // Attr2Test
}

class SomeClass {
  static attributes = [AttrTest, Attr2Test]
}

在这里你可以注意到,我有valueType哪个持有真正的类型。我也知道这个名字,如果它是可选的。(如果required存在且设置为 true,则为必需)

概念和我不工作的解决方案

我的想法是遍历attributes数组,将值映射到名称并使其可选。

  1. 键入以过滤可选属性
export type ValueOf<T> = T[keyof T];
type FilterOptionalAttribute<Attr extends AttributeClass> = ValueOf<Attr["required"]> extends false | undefined | null ? Attr : never
  1. 键入以过滤所需的属性
type FilterRequiredAttribute<Attr extends AttributeClass> = FilterOptionalAttribute<Attr> extends never ? Attr : never
  1. 要从 Type 转换为原始类型的类型
type ExtractPrimitiveType<A> =
  A extends StringConstructor ? string :
    A extends NumberConstructor ? number :
      A extends BooleanConstructor ? boolean :
        never
  1. 从类转换为键值对象的类型(必需 + 可选)
type AttributeDataType<Attr extends AttributeClass> = { [K in Attr["attrName"]]: ExtractPrimitiveType<Attr["valueType"]> }

type OptionalAttributeDataType<Attr extends AttributeClass> = { [K in Attr["attrName"]]?: ExtractPrimitiveType<Attr["valueType"]> }
  1. 把它粘在一起 + 一些东西来推断数组类型
type UnboxAttributes<AttrList> = AttrList extends Array<infer U> ? U : AttrList;

type DataType<AttributeList extends AttributeClass[]> = OptionalAttributeDataType<FilterOptionalAttribute<UnboxAttributes<AttributeList>>> & AttributeDataType<FilterRequiredAttribute<UnboxAttributes<AttributeList>>>

我对输出有什么期望

class SomeClass {
  static attributes = [AttrTest, Attr2Test]
}

// notice double equals
const mapped: DataType<typeof SomeClass.attributes> == {
  test: string
  test2?: number
}

IDE 显示的内容

使用 IntelliJ IDEA Ultimate

// notice double equals
const mapped: DataType<typeof SomeClass.attributes> == {
  test: string | number
  test2: number | number
}

我已经花了 5 个小时来解决它。似乎我错过了一些重要的东西。我要感谢所有给我任何提示的人我做错了什么。

有两个问题:

TypeScript Playground的链接

标签: typescripttypescript-typings

解决方案


我有其他但有效的解决方案......

// Declare constructor type
type Constructor<T> = new (...args: any[]) => T;

// Declare support attribute types
type SupportTypes = [String, Number, Boolean];

// Attribyte class
class AttributeClass<K extends string, T extends SupportTypes[number], R extends boolean = false> {
  constructor(
    readonly attrName: K,
    readonly valueType: Constructor<T>,
    readonly required?: R,
  ) {
  }
}


// Declare test attributes
const AttrTest = new AttributeClass('test', String, true);
const Attr2Test = new AttributeClass('test2', Number);

const attributes = [AttrTest, Attr2Test];

// Unwrap instance of AttributeClass, to object
type UnwrapAttribute<T> = T extends AttributeClass<infer K, infer T, infer R> ? (
  R extends true ? {
    [key in K]: T;
  } : {
    [key in K]?: T;
  }
) : never;

// Transform union to intersection
// Example: UnionToIntersection<{a: string} | {b: number}> => {a: string, b: number}
type UnionToIntersection<U> = ((U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never);

// Transform tuple to intersection
// Example: TupleToIntersection<[{a: string}, {b: number}]> => {a: string, b: number}
type TupleToIntersection<U extends Array<any>> = UnionToIntersection<U[number]>;

// Map array of attributes
type MapAttributes<ArrT extends Array<AttributeClass<any, any, any>>> = TupleToIntersection<{
  [I in keyof ArrT]: UnwrapAttribute<ArrT[I]>;
}>;

// Result
const mapped: MapAttributes<typeof attributes> = {
  test: '123',
  test2: 123,
};

操场


推荐阅读