Tengo una definición de interfaz de la siguiente manera
export interface RoutingMap { ticket: { id: number }, car: { model: string, make: number } } Quiero poder crear una interfaz para un objeto, que tiene 2 propiedades: 1 es una clave en RoutingMap y la otra es cualquiera que sea el tipo de esa Key en RoutingMap .
En pseudo-código lo imagino luciendo así
export interface RoutingMessage { resource: keyof RoutingMap, params: RoutingMap["resource"] }El objetivo final es que yo sea capaz de construir objetos como este
const msg: RoutingMessage = { resource: "ticket", params: { id: 10 } } const invalidMsg: RoutingMessage = { resource: "something", //TS error - "something" is not a key of routing map params: { id: 10 } } const invalidMsg2: RoutingMessage = { resource: "car" params: { model: "tesla", make: true } //TS error - "make" is not a boolean } const invalidMsg3: RoutingMessage = { resource: "car" params: { id: 123 } //TS error - id is not assinable to {model: string, make: boolean} } RoutingMap es algo que se extendería con el tiempo, y las personas deberían poder crear objetos específicos, en función de las claves (y sus valores)
Como puede ver en el ejemplo de pseudocódigo, puedo establecer una restricción para el recurso, pero necesito una forma de restringir la propiedad params para permitir solo objetos que coincidan con la firma de la Key en RoutingMap
Debido a que hay un número finito de claves que coinciden con la clave de keyof RoutingMap , puede escribir RoutingMessage como una unión de tipos de objetos donde cada elemento de la unión corresponde a una clave particular, así:
type RoutingMessage = { resource: "ticket"; params: { id: number; }; } | { resource: "car"; params: { model: string; make: number; }; } Puede calcular RoutingMessage programación desde RoutingMap escribiéndolo como un "tipo de objeto distributivo" (terminología tomada de microsoft/TypeScript#47109 ) donde creamos un tipo asignado donde cada propiedad con la clave K en RoutingMap se asigna al tipo de objeto deseado para ese key, y luego lo indexamos inmediatamente con keyof keyof RoutingMap , produciendo la unión deseada.
type RoutingMessage = { [K in keyof RoutingMap]: { resource: K, params: RoutingMap[K] } }[keyof RoutingMap]; Con la definición anterior, RoutingMessage obtendrá nuevos miembros de unión cada vez que se agregue una nueva propiedad a RoutingMap , según se desee.
Asegurémonos de que se comporta como usted quiere:
const msg: RoutingMessage = { resource: "ticket", params: { id: 10 } }; // okay const invalidMsg: RoutingMessage = { resource: "something", // error! //~~~~~~ <-- Type '"something"' is not assignable to type '"ticket" | "car"' params: { id: 10 } }; const invalidMsg2: RoutingMessage = { resource: "car", params: { model: "tesla", make: true } // error! // --------------------> ~~~~ // Type 'boolean' is not assignable to type 'number'. }; const invalidMsg3: RoutingMessage = { resource: "car", params: { id: 123 } // error! // -----> ~~~~~~~ // Type '{ id: number; }' is not assignable to type '{ model: string; make: number; }' };¡Se ve bien!
export interface RoutingMessage { resource: keyof RoutingMap, params: RoutingMap[this["resource"]] }