获取Typescript接口的键作为字符串数组

时间:2017-05-11 07:53:52

标签: javascript typescript

我在Lovefield和他们各自的界面中有很多表,他们有哪些列。
实施例:

export interface IMyTable {
  id: number;
  title: string;
  createdAt: Date;
  isDeleted: boolean;
}

我希望在这样的数组中包含此接口的属性名称:

var IMyTable = ["id", "title", "createdAt", "isDeleted"];

我无法直接根据接口IMyTable创建一个对象/数组,因为我会动态获取表的接口名称。因此,我需要在界面中迭代这些属性并从中获取一个数组。

我如何实现这一结果。

11 个答案:

答案 0 :(得分:17)

我有一个类似的问题,我有一个庞大的属性列表,我想同时拥有一个接口和一个对象。

注意:我不想写两次属性(用键盘输入)!只需干燥。


这里要注意的一件事是,接口在编译时是强制类型,而对象大多数是在运行时。 (Source

another answer中的@derek所述,界面对象的公分母可以是同时提供类型的类>和

因此,TL; DR,以下代码应满足需求:

class MyTableClass {
    // list the propeties here, ONLY WRITTEN ONCE
    id = "";
    title = "";
    isDeleted = false;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

// This is the pure interface version, to be used/exported
interface IMyTable extends MyTableClass { };

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

// Props type as an array, to be exported
type MyTablePropsArray = Array<keyof IMyTable>;

// Props array itself!
const propsArray: MyTablePropsArray =
    Object.keys(new MyTableClass()) as MyTablePropsArray;

console.log(propsArray); // prints out  ["id", "title", "isDeleted"]


// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

// Example of creating a pure instance as an object
const tableInstance: MyTableClass = { // works properly!
    id: "3",
    title: "hi",
    isDeleted: false,
};

({Here是Typescript Playground中的上述代码,可以播放更多内容)

PS。如果您不想为类中的属性分配初始值,而是希望保留类型,则可以使用构造函数:

class MyTableClass {
    // list the propeties here, ONLY WRITTEN ONCE
    constructor(
        readonly id?: string,
        readonly title?: string,
        readonly isDeleted?: boolean,
    ) {}
}

console.log(Object.keys(new MyTableClass()));  // prints out  ["id", "title", "isDeleted"] 

Constructor Trick in TypeScript Playground

答案 1 :(得分:14)

也许为时已晚,但是在TypeScript 2.1版中,您可以像这样使用key of

interface Person {
    name: string;
    age: number;
    location: string;
}

type K1 = keyof Person; // "name" | "age" | "location"
type K2 = keyof Person[];  // "length" | "push" | "pop" | "concat" | ...
type K3 = keyof { [x: string]: Person };  // string

来源:https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html#keyof-and-lookup-types

答案 2 :(得分:12)

不能。接口在运行时不存在。

的解决方法

创建一个类型的变量并在其上使用Object.keys

答案 3 :(得分:11)

安全变体

使用安全编译时检查从接口创建键数组或tuple,需要一些创新。类型会在运行时删除,并且对象类型(无序,已命名)无法转换为元组类型(有序,未命名)without resorting to non-supported techniques

与其他答案的比较

在给定引用对象类型为IMyTable的元组项重复或丢失的情况下,此处提出的变体都考虑/触发了编译错误。例如,声明数组类型为(keyof IMyTable)[]不能捕获这些错误。

此外,它们不需要a specific library(最后一个变体使用ts-morph,我将其视为通用编译器包装器)发出一个元组类型as opposed to an object(仅第一个解决方案创建)数组)或宽数组类型(与these answers比较),最后是don't need classes

变量1:简单类型数组

// Record type ensures, we have no double or missing keys, values can be neglected
function createKeys(keyRecord: Record<keyof IMyTable, any>): (keyof IMyTable)[] {
  return Object.keys(keyRecord) as any
}

const keys = createKeys({ isDeleted: 1, createdAt: 1, title: 1, id: 1 })
// const keys: ("id" | "title" | "createdAt" | "isDeleted")[]

+最简单的+-手册,带有自动完成的-数组,没有元组

Playground

如果您不喜欢创建记录,请查看this alternative with Set and assertion types


变量2:具有辅助功能的元组

function createKeys<T extends readonly (keyof IMyTable)[] | [keyof IMyTable]>(
    t: T & CheckMissing<T, IMyTable> & CheckDuplicate<T>): T {
    return t
}

+元组+-手册,具有自动完成的+-更高级,更复杂的类型

Playground

说明

createKeys进行编译时检查by merging带有附加断言类型的函数参数类型,这些断言类型针对不适当的输入发出错误。 (keyof IMyTable)[] | [keyof IMyTable]"black magic" way,用于强制从被调用方推论元组而不是数组。或者,您可以在呼叫方使用const assertions / as const

CheckMissing检查是否T遗漏了U的密钥:

type CheckMissing<T extends readonly any[], U extends Record<string, any>> = {
    [K in keyof U]: K extends T[number] ? never : K
}[keyof U] extends never ? T : T & "Error: missing keys"

type T1 = CheckMissing<["p1"], {p1:any, p2:any}> //["p1"] & "Error: missing keys"
type T2 = CheckMissing<["p1", "p2"], { p1: any, p2: any }> // ["p1", "p2"]

注意:T & "Error: missing keys"仅用于解决IDE错误。您也可以写neverCheckDuplicates检查双元组项目:

type CheckDuplicate<T extends readonly any[]> = {
    [P1 in keyof T]: "_flag_" extends
    { [P2 in keyof T]: P2 extends P1 ? never :
        T[P2] extends T[P1] ? "_flag_" : never }[keyof T] ?
    [T[P1], "Error: duplicate"] : T[P1]
}

type T3 = CheckDuplicate<[1, 2, 3]> // [1, 2, 3]
type T4 = CheckDuplicate<[1, 2, 1]> 
// [[1, "Error: duplicate"], 2, [1, "Error: duplicate"]]

注意:有关元组中唯一项检查的更多信息,请参见this post。借助TS 4.1,我们还可以命名错误字符串中缺少的键-看看this Playground


变量3:递归类型

在4.1版中,TypeScript正式支持conditional recursive types,也可以在此处使用。但是,由于组合复杂性,类型计算非常昂贵-对于5-6个以上的项目,性能会大大降低。为了完整起见,我列出了这种替代方法(Playground):

type Prepend<T, U extends any[]> = [T, ...U] // TS 4.0 variadic tuples

type Keys<T extends Record<string, any>> = Keys_<T, []>
type Keys_<T extends Record<string, any>, U extends PropertyKey[]> =
  {
    [P in keyof T]: {} extends Omit<T, P> ? [P] : Prepend<P, Keys_<Omit<T, P>, U>>
  }[keyof T]

const t1: Keys<IMyTable> = ["createdAt", "isDeleted", "id", "title"] // ✔

+元组+-手册,具有自动完成功能+无辅助功能--的性能


变体4:代码生成器/ TS编译器API

在这里选择

ts-morph,因为它是original TS compiler API的替代方法,有点简单。当然,您也可以直接使用编译器API。让我们看一下生成器代码:

// ./src/mybuildstep.ts
import {Project, VariableDeclarationKind, InterfaceDeclaration } from "ts-morph";

const project = new Project();
// source file with IMyTable interface
const sourceFile = project.addSourceFileAtPath("./src/IMyTable.ts"); 
// target file to write the keys string array to
const destFile = project.createSourceFile("./src/generated/IMyTable-keys.ts", "", {
  overwrite: true // overwrite if exists
}); 

function createKeys(node: InterfaceDeclaration) {
  const allKeys = node.getProperties().map(p => p.getName());
  destFile.addVariableStatement({
    declarationKind: VariableDeclarationKind.Const,
    declarations: [{
        name: "keys",
        initializer: writer =>
          writer.write(`${JSON.stringify(allKeys)} as const`)
    }]
  });
}

createKeys(sourceFile.getInterface("IMyTable")!);
destFile.saveSync(); // flush all changes and write to disk

使用tsc && node dist/mybuildstep.js编译并运行此文件后,将生成具有以下内容的文件./src/generated/IMyTable-keys.ts

// ./src/generated/IMyTable-keys.ts
const keys = ["id","title","createdAt","isDeleted"] as const;

+自动生成的解决方案+可扩展为多个属性+无辅助功能+元组-需要额外的构建步骤-熟悉编译器API

答案 4 :(得分:4)

您需要创建一个实现您的接口的类,将其实例化,然后使用Object.keys(yourObject)来获取属性。

export class YourClass implements IMyTable {
    ...
}

然后

let yourObject:YourClass = new YourClass();
Object.keys(yourObject).forEach((...) => { ... });

答案 5 :(得分:2)

不要将IMyTable定义为接口,而是尝试将其定义为类。在打字稿中,您可以使用类似接口的类。

因此,对于您的示例,定义/生成您的类:

export class IMyTable {
    constructor(
        public id = '',
        public title = '',
        public createdAt: Date = null,
        public isDeleted = false
    )
}

将其用作界面:

export class SomeTable implements IMyTable {
    ...
}

获取密钥:

const keys = Object.keys(new IMyTable());

答案 6 :(得分:2)

这应该有效

var IMyTable: Array<keyof IMyTable> = ["id", "title", "createdAt", "isDeleted"];

var IMyTable: (keyof IMyTable)[] = ["id", "title", "createdAt", "isDeleted"];

答案 7 :(得分:1)

以下要求您自己列出密钥,但是至少TypeScript将强制IUserProfileIUserProfileKeys具有完全相同的密钥(Required<T> was added in TypeScript 2.8):

export interface IUserProfile  {
  id: string;
  name: string;
};
type KeysEnum<T> = { [P in keyof Required<T>]: true };
const IUserProfileKeys: KeysEnum<IUserProfile> = {
  id: true,
  name: true,
};

答案 8 :(得分:0)

这很艰难!谢谢大家,为您提供帮助。

我的需要是将接口的键作为字符串数组来获取,以简化mocha / chai脚本。尚不关心在应用程序中的使用(因此),因此不需要创建ts文件。感谢ford04的帮助,他的解决方案above是一个巨大的帮助,它的工作原理非常完美,没有编译器黑客。这是修改后的代码:

选项2:基于TS编译器API(ts-morph)的代码生成器

节点模块

npm install --save-dev ts-morph

keys.ts

注意:这假设所有ts文件都位于./src的根目录中,并且没有子文件夹,请相应调整

import {
  Project,
  VariableDeclarationKind,
  InterfaceDeclaration,
} from "ts-morph";

// initName is name of the interface file below the root, ./src is considered the root
const Keys = (intName: string): string[] => {
  const project = new Project();
  const sourceFile = project.addSourceFileAtPath(`./src/${intName}.ts`);
  const node = sourceFile.getInterface(intName)!;
  const allKeys = node.getProperties().map((p) => p.getName());

  return allKeys;
};

export default Keys;

用法

import keys from "./keys";

const myKeys = keys("MyInterface") //ts file name without extension

console.log(myKeys)

答案 9 :(得分:-1)

您无法做到。接口在运行时不存在(如@basarat这样。)

现在,我正在处理以下内容:

const IMyTable_id = 'id';
const IMyTable_title = 'title';
const IMyTable_createdAt = 'createdAt';
const IMyTable_isDeleted = 'isDeleted';

export const IMyTable_keys = [
  IMyTable_id,
  IMyTable_title,
  IMyTable_createdAt,
  IMyTable_isDeleted,
];

export interface IMyTable {
  [IMyTable_id]: number;
  [IMyTable_title]: string;
  [IMyTable_createdAt]: Date;
  [IMyTable_isDeleted]: boolean;
}

答案 10 :(得分:-2)

// declarations.d.ts
export interface IMyTable {
      id: number;
      title: string;
      createdAt: Date;
      isDeleted: boolean
}
declare var Tes: IMyTable;
// call in annother page
console.log(Tes.id);