typescript 从对象创建接口定义

a1o7rhls  于 2023-01-10  发布在  TypeScript
关注(0)|答案(2)|浏览(109)

假设我有以下定义:

export enum Category {
  car = 'car',
  truck = 'truck',
}

export interface IProperty {
  propertyName: string,
  propertyLabel: string,
  propertyType: 'string' | 'number',
}

export interface CategoryDefinition {
  category: Category,
  label: string,
  properties: IProperty[]
}

然后有这样的定义:

export class Definitions {

  public static get car(): CategoryDefinition {
    return {
      category: Category.car,
      label: "Car",
      properties: [
        {
          propertyName: 'weight',
          propertyLabel: 'Weight',
          propertyType: 'number'
        },
        {
          propertyName: 'color',
          propertyLabel: 'Color',
          propertyType: 'string'
        }
      ],
    };
  }

  public static get truck(): CategoryDefinition {
    return {
      category: Category.truck,
      label: "Truck",
      properties: [
        {
          propertyName: 'payload',
          propertyLabel: 'Payload',
          propertyType: 'number'
        },
        {
          propertyName: 'axles',
          propertyLabel: 'Axles',
          propertyType: 'number'
        }
      ],
    };
  }
}

现在,我想从这个定义中生成接口,结果应该是:

export interface Car {
  weight: number,
  color: string
}

export interface Truck{
  payload: number,
  axles: number
}

理想的做法是:

export interface Car = createInterface('Car', Definitions.car.properties);
export interface Truck = createInterface('Truck', Definitions.truck.properties);

原因是我必须处理其中的30 - 40个定义。我当然可以自己定义它们的接口(API响应),但之后我必须重新定义它们的属性以用于UI显示,这很容易出错。这样我只需要定义它们的属性,就可以生成它们相应的接口。
我发现an answer可能暗示了正确的方向,但是我觉得它不太一样,我不能让它工作。
如果有帮助,定义将是固定的(即不动态更改/更新)。
任何帮助都将不胜感激。

4nkexdtk

4nkexdtk1#

您可能应该将定义类型设置为只读(因为您不需要在运行时更改这些类型):

export interface IProperty {
  readonly propertyName: string,
  readonly propertyLabel: string,
  readonly propertyType: 'string' | 'number',
}

export interface CategoryDefinition {
  readonly category: Category,
  readonly label: string,
  readonly properties: readonly IProperty[]
}

另一个需要做的更改是使用as const satisfies CategoryDefinition,而不是将返回类型注解为CategoryDefinition,这样可以在确保返回类型为CategoryDefinition的同时“保留”值。

export class Definitions {
  public static get car() {
    return {
      category: Category.car,
      label: "Car",
      properties: [
        {
          propertyName: 'weight',
          propertyLabel: 'Weight',
          propertyType: 'number'
        },
        {
          propertyName: 'color',
          propertyLabel: 'Color',
          propertyType: 'string'
        }
      ],
    } as const satisfies CategoryDefinition;
  }

  public static get truck() {
    return {
      category: Category.truck,
      label: "Truck",
      properties: [
        {
          propertyName: 'payload',
          propertyLabel: 'Payload',
          propertyType: 'number'
        },
        {
          propertyName: 'axles',
          propertyLabel: 'Axles',
          propertyType: 'number'
        }
      ],
    } as const satisfies CategoryDefinition;
  }
}

然后我们需要定义一个类型,将属性类型Map到它们的实际类型:

type TypeMap = {
    string: string;
    number: number;
    // etc ...
};

最后,我们可以Map定义属性并为每个字段检索正确的类型:

type FromDefinition<D extends CategoryDefinition> = {
    [T in D["properties"][number] as T["propertyName"]]: TypeMap[T["propertyType"]];
};

type Car = FromDefinition<typeof Definitions["car"]>;
//   ^? { weight: number; color: string }
type Truck = FromDefinition<typeof Definitions["truck"]>;
//   ^? { payload: number; axles: number }

Playground
你的Definitions类真的需要是一个类吗?它可以是:

const Definitions = {
    car: { ... },
    truck: { ... },
} as const satisfies Record<string, CategoryDefinition>;

这样更容易阅读和维护。

ipakzgxi

ipakzgxi2#

您必须为此创建自定义类型,这将需要一些工作(通常建议使用Zod之类的第三方库来为您也希望在TypeScript中访问的类型定义“模式”。但是,如果您不想使用第三方库,我将看看是否可以找到一些适合您的用例的类型。
这是个工作场所
有一些事情你需要调整你的定义(vera在另一个答案中解释了),但这里是我想出的“神奇”类型:

type TypeMap = {
  string: string,
  number: number,
}

export type GetDefinitionType<D extends CategoryDefinition> = {
  [Property in D['properties'][number] as Property['propertyName']]:
  TypeMap[Property['propertyType']]
}

它所做的是循环遍历所有属性,然后使用TypeMap类型(“string”-〉string,“number”到number)将字符串化类型“Map”到实际的TypeScript类型(这个想法归功于vera)
问题是,随着类型变得越来越复杂,您需要自己维护这些值的Map,这就是为什么存在像zod这样的第三方库的原因;来为您处理这些值-〉类型Map(我推荐使用)。

相关问题