Obtener claves de una interfaz Typecript como una matriz de cadenas

Resuelto Tushar Shukla asked hace 7 años • 0 respuestas

Tengo muchas tablas en Lovefield y sus respectivas interfaces para las columnas que tienen.

Ejemplo:

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

Me gustaría tener los nombres de las propiedades de esta interfaz en una matriz como esta:

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

No puedo crear un objeto/matriz basado IMyTabledirectamente en la interfaz, lo que debería funcionar porque obtendría los nombres de las interfaces de las tablas de forma dinámica. Por lo tanto, necesito iterar sobre estas propiedades en la interfaz y obtener una matriz.

¿Cómo logro este resultado?

Tushar Shukla avatar May 11 '17 14:05 Tushar Shukla
Aceptado

A partir de TypeScript 2.3 (o debería decir 2.4 , ya que en 2.3 esta característica contiene un error que se ha solucionado en [email protected] ), puede crear un transformador personalizado para lograr lo que desea hacer.

De hecho, ya he creado un transformador personalizado que permite lo siguiente.

https://github.com/kimamula/ts-transformer-keys

import { keys } from 'ts-transformer-keys';

interface Props {
  id: string;
  name: string;
  age: number;
}
const keysOfProps = keys<Props>();

console.log(keysOfProps); // ['id', 'name', 'age']

Desafortunadamente, los transformadores personalizados no son tan fáciles de usar actualmente. Debe usarlos con la API de transformación de TypeScript en lugar de ejecutar el comando tsc. Hay un problema al solicitar compatibilidad con un complemento para transformadores personalizados.

kimamula avatar May 11 '2017 17:05 kimamula

Me enfrenté a un problema similar: tenía una lista gigante de propiedades que quería tener como interfaz (tiempo de compilación) y como objeto (tiempo de ejecución).

NOTA: ¡No quería escribir (escribir con el teclado) las propiedades dos veces! SECO.


Una cosa a tener en cuenta aquí es que las interfaces son tipos obligatorios en tiempo de compilación, mientras que los objetos son en su mayoría en tiempo de ejecución. ( Fuente )

Como @derek mencionó en otra respuesta , el denominador común de la interfaz y el objeto puede ser una clase que sirva tanto para un tipo como para un valor .

Entonces, TL;DR, el siguiente fragmento de código debería satisfacer las necesidades:

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,
};

( Aquí está el código anterior en Typecript Playground para jugar más)

PD. Si no desea asignar valores iniciales a las propiedades de la clase y permanecer con el tipo, puede hacer el truco del constructor:

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"] 

Truco del constructor en TypeScript Playground .

Aidin avatar Jan 19 '2020 03:01 Aidin

Tal vez sea demasiado tarde, pero en la versión 2.1 de TypeScript puedes usarlo keyofpara obtener un tipo como este:

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

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

Nathan Gabriel avatar Jun 19 '2020 19:06 Nathan Gabriel

Lo siguiente requiere que usted enumere las claves por su cuenta, pero al menos TypeScript aplicará IUserProfiley IUserProfileKeystendrá exactamente las mismas claves ( Required<T>se agregó en 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,
};
Maciek Wawro avatar Jan 22 '2019 12:01 Maciek Wawro

Variantes seguras

Crear una matriz o tupla de claves desde una interfaz con comprobaciones de seguridad en tiempo de compilación requiere un poco de creatividad. Los tipos se borran en tiempo de ejecución y los tipos de objetos (desordenados, con nombre) no se pueden convertir en tipos de tupla (ordenados, sin nombre) sin recurrir a técnicas no compatibles .

Comparación con otras respuestas

Todas las variantes propuestas aquí consideran/activan un error de compilación en caso de elementos de tupla duplicados o faltantes dado un tipo de objeto de referencia como IMyTable. Por ejemplo, declarar un tipo de matriz (keyof IMyTable)[]no puede detectar estos errores.

Además, no requieren una biblioteca específica (la última variante usa ts-morph, que yo consideraría un contenedor de compilador genérico), emiten un tipo de tupla en lugar de un objeto (solo la primera solución crea una matriz) o un tipo de matriz amplia (en comparación con estas respuestas ) y por último no necesito clases .

Variante 1: matriz escrita simple

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

+manual más sencillo +-con matriz de autocompletado -, sin tupla

Patio de juegos

Si no te gusta crear un registro, echa un vistazo a esta alternativa con Sety tipos de aserción .


Variante 2: Tupla con función auxiliar

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

+manual de tuplas +-con autocompletado +-tipos más avanzados y complejos

Patio de juegos

Explicación

createKeysrealiza comprobaciones en tiempo de compilación fusionando el tipo de parámetro de función con tipos de aserción adicionales, que emiten un error por entrada no adecuada. (keyof IMyTable)[] | [keyof IMyTable]es una forma de "magia negra" para forzar la inferencia de una tupla en lugar de una matriz desde el lado del destinatario. Alternativamente, puede usar afirmaciones constantes/as const del lado de la persona que llama.

CheckMissingcomprueba, si Tfaltan claves de 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"]

Nota: T & "Error: missing keys"es sólo para errores agradables de IDE. También podrías escribir never. CheckDuplicatescomprueba elementos de doble tupla:

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"]]

Nota: En esta publicación encontrará más información sobre comprobaciones de artículos únicos en tuplas . Con TS 4.1 , también podemos nombrar las claves que faltan en la cadena de error; eche un vistazo a este Playground .


Variante 3: tipo recursivo

Con la versión 4.1, TypeScript admite oficialmente tipos recursivos condicionales , que potencialmente también pueden usarse aquí. Sin embargo, el cálculo de tipos es costoso debido a la complejidad combinatoria: el rendimiento se degrada enormemente para más de 5 o 6 elementos. Enumero esta alternativa para que esté completa ( 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"] // ✔

+manual de tupla +-con autocompletado sin rendimiento +de función auxiliar--


Variante 4: Generador de código/API del compilador TS

Aquí se elige ts-morph , ya que es una alternativa contenedora un poco más simple que la API del compilador TS original . Por supuesto, también puedes utilizar la API del compilador directamente. Veamos el código del generador:

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

Después de compilar y ejecutar este archivo , se genera tsc && node dist/mybuildstep.jsun archivo con el siguiente contenido:./src/generated/IMyTable-keys.ts

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

+solución de generación automática +escalable para múltiples propiedades +sin función auxiliar +tupla -paso de compilación adicional -necesita familiaridad con la API del compilador

ford04 avatar Mar 30 '2020 14:03 ford04