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!
