Búsqueda de sitios web

typescript - Mapear un arreglo a una interfaz


Digamos que tengo un arreglo que se ve así:

const options =  [
 {
   name: 'foo',
   type: 'boolean'
 },

 {
   name: 'bar',
   type: 'string'
 },

 {
   name: 'bar', // should be baz not bar
   type: 'number'
 }

]

Estoy buscando usar este arreglo como una interfaz que se vería algo así:

export interface Opts {
   foo: boolean,
   bar: string,
   baz: number
}

así que probablemente tendría que ser algo como:

export type Opts = manipulate(typeof options);

donde manipulate es alguna característica mágica de TS que espero descubrir.

Creo que este es un buen lugar para comenzar: https://blog.mariusschulz.com/2017/01/20/typescript-2-1-mapped-types

pero es difícil de averiguar.

Respuesta #1

Sí, puedes hacer esto, pero requiere tanto tipos mapeados como condicionales.

Primero necesitas un tipo que represente tu mapeo desde nombres de tipos como "boolean" a tipos reales como boolean.

type TypeMapping = {
  boolean: boolean,
  string: string,
  number: number,
  // any other types
}

Entonces necesitas una función auxiliar que se asegure de que el valor de tu options no amplíe sus tipos para las propiedades name y type a string. (Si inspeccionas tu valor de options, su tipo es algo como {name: string, type: string}[], que ha perdido el seguimiento de los valores particulares de name y type que deseas). Puedes usar restricciones genéricas para hacer esto, de la siguiente manera:

const asOptions = <K extends keyof any, 
  T extends Array<{ name: K, type: keyof TypeMapping }>>(t: T) => t;

Veamos si funciona:

const options = asOptions([
  {
    name: 'foo',
    type: 'boolean'
  },

  {
    name: 'bar',
    type: 'string'
  },

  {
    name: 'bar',
    type: 'number'
  }
]);

Si inspeccionas eso, verás que ahora es un arreglo de tipos donde cada uno de name y type se ha reducido a los literales "foo", "bar", "number", etc.

Finalmente tenemos que hacer esa función de tipo manipulate que quieres. La llamaré OptionsToType:

type OptionsToType<T extends Array<{ name: keyof any, type: keyof TypeMapping }>>
  = { [K in T[number]['name']]: TypeMapping[Extract<T[number], { name: K }>['type']] }

Eso podría parecer muy complicado. Veamos si puedo desglosarlo.

T extends Array<{ name: keyof any, type: keyof TypeMapping }>

significa que T debe ser un arreglo de objetos con un campo name como una clave de objeto, y un campo type como una clave del tipo TypeMapping indicado arriba.

  = { [K in T[number]['name']]: ... }

iterar a través de todos los nombres de clave en la propiedad name de cada elemento del arreglo T

  Extract<T[number], { name: K }>

significa "encontrar el elemento de T que corresponde al nombre K"...

  Extract<T[number], { name: K }>['type']

...y consulta su propiedad 'type'...

  TypeMapping[Extract<T[number], { name: K }>['type']]

...y usa eso como un índice en el tipo TypeMapping.

Está bien, veamos si funciona:

export type Opts = OptionsToType<typeof options>;

Y si inspeccionas Opts ves:

{
    foo: boolean;
    bar: string | number;
}

tal como esperabas--- uh, espera, ¿por qué la propiedad bar es de tipo string | number? Oh, porque pusiste bar en options dos veces. Cambia la segunda a baz y será lo que esperas.

Está bien, espero que eso ayude. ¡Buena suerte!

Artículos relacionados