TypeScript: Buscar el tipo de un campo basado en otro campo
Digamos que tengo una interfaz así:
interface Cat {
weight: number;
name: string;
adoptable: boolean;
}
Quiero definir la siguiente interfaz que se puede usar para establecer valores en Cat:
interface CatUpdate {
field: keyof Cat;
value: ???
}
y quiero que TypeScript haga cumplir que el valor sea del tipo apropiado según el campo. Por ejemplo, esto debería ser inválido:
const x: CatUpdate = {
field: "name",
value: 12
}
¿Cómo defino el tipo de CatUpdate.value?
Respuesta #1
Mi inclinación sería hacer que CatUpdate sea un tipo unión y no una interfaz, así:
interface Cat {
weight: number;
name: string;
adoptable: boolean;
}
type PropUpdate<T extends object> = {
[K in keyof T]: { field: K; value: T[K] }
}[keyof T];
type CatUpdate = PropUpdate<Cat>;
/*
type CatUpdate = {
field: "weight";
value: number;
} | {
field: "name";
value: string;
} | {
field: "adoptable";
value: boolean;
}
*/
Eso debería funcionar lo suficientemente bien en el caso de uso que mencionaste:
const x: CatUpdate = {
field: "name",
value: 12
} // error!
Y deberías poder componer esos (CatUpdate | DogUpdate) si lo necesitas, aunque sin un caso de uso particular es difícil saber si tal composición es apropiada (¿Se pueden discriminar Cat y Dog entre sí? Es decir, ¿es Cat & Dog imposible? Si Cat & Dog es posible, entonces CatUpdate | DogUpdate solo se puede usar para actualizar un Cat & Dog. Si es imposible, entonces CatUpdate | DogUpdate probablemente debería reemplazarse con una unión discriminada como (CatUpdate & {kind: "CatUpdate"}) | (DogUpdate & {kind: "DogUpdate"}).)
Enlace al código
Respuesta #2
Una manera es usar un tipo genérico:
interface CatUpdate<T extends keyof Cat> {
field: T;
value: Cat[T];
}
Funciona, pero significa que necesitarás ser un poco más verboso al crear el objeto.
// Error
const x: CatUpdate<"name"> = {
field: "name",
value: 12
}
// No error
const y: CatUpdate<"name"> = {
field: "name",
value: "Dave"
}
Una solución sería usar una función para la instanciación que pueda inferir el tipo basado en los parámetros:
function getCatUpdate<T extends keyof Cat>(field: T, value: Cat[T]): CatUpdate<T> {
return {
field,
value
}
}
Como podrías llamar:
// Error
const x = getCatUpdate("name", 12);
// No error
const y = getCatUpdate("name", "Dave");
