Get keys of a Typescript interface as array of strings

I've a lot of tables in Lovefield and their respective Interfaces for what columns they have.
Example:

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

I'd like to have the property names of this interface in an array like this:

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

I cannot make an object/array based on the interface IMyTable directly which should do the trick because i'd be getting the interface names of the tables dynamically. Hence i need to iterate over these properties in the interface and get an array out of it.

How do i achieve this result.

Answers:

Answer

There is no easy way to create a keys array from an interface. Types are erased at run-time and object types (unordered, named) cannot be converted to tuple types (ordered, unnamed) without some sort of hack.


Option 1: Manual approach

// 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")[]

(+) simple   (-) array return type, no tuple   (+-) manual writing with auto-completion

Extension: We could try to be fancy and use recursive types to generate a tuple. This only worked for me for a couple of props (~5,6) until performance degraded massively. Deeply nested recursive type also aren't officially supported by TS - I list this example here for the sake of completeness.


Option 2: Code generator based on TS compiler API (ts-morph)

// ./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

After we compile and run this file with tsc && node dist/mybuildstep.js, a file ./src/generated/IMyTable-keys.ts with following content is generated:

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

(+) automatic solution   (+) exact tuple type   (-) requires build-step


PS: I have chosen ts-morph, as it is a simple alternative to the original TS compiler API.

Answer

Maybe it's too late, but in version 2.1 of typescript you can use key of like this:

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

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

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.