Obtener claves de una interfaz Typecript como una matriz de cadenas
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 IMyTable
directamente 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?
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.
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 .
Tal vez sea demasiado tarde, pero en la versión 2.1 de TypeScript puedes usarlo keyof
para 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
Lo siguiente requiere que usted enumere las claves por su cuenta, pero al menos TypeScript aplicará IUserProfile
y IUserProfileKeys
tendrá 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,
};
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 Set
y 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
createKeys
realiza 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.
CheckMissing
comprueba, si T
faltan 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
. CheckDuplicates
comprueba 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.js
un 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