typescript - ¿Guardia de tipo para valor de enum permitido con cadena dinámica?
Estoy trabajando con algunos datos de un tercero que quiero convertir en un mapa por id, pero solo si los datos son válidos. Tengo un enum de propiedades permitidas, pero no puedo averiguar cómo comprobar que los datos son válidos de una manera que el compilador permita. He intentado comprobarlo usando una declaración if con el operador in en el enum:
/** Allowed action values */
enum Actions {
Update = 'Update',
Delete = 'Delete'
}
/** Validated app events */
type AppEvent = {
id: string;
action: Actions;
}
/** 3rd party data I want to validate */
type RawEvent = {
id: string;
// ❗️I want to make sure this string is an allowed enum
action: string
}
type AppEventsById = {
[id: string]: AppEvent
}
const possiblyInvalidEvents: RawEvent[] = [
{
id: 'typescript',
action: 'Update'
},
{
id: 'uh oh',
action: 'oops'
}
]
const eventsById: AppEventsById = {}
possiblyInvalidEvents.forEach(event => {
// ❓Here I'm attempting to check that 3rd party action field is one of the allowed enums
if (event.action in Actions) {
eventsById[event.id] = {
id: event.id,
// 💥Type 'string' is not assignable to type 'Actions'
action: event.action
}
}
})
// => I want eventsById to include the { id: 'typescript' } object, but not { id: 'uh oh' }
El intento de asignación a action produce este error: Type 'string' is not assignable to type 'Actions'.
Respuesta #1
Quieres una función de guardia de tipo definida por el usuario para verificar si un string es un miembro válido de Actions. Esta es una manera de decirle explícitamente al compilador que cierta expresión con valor boolean debería usarse para reducir el tipo de un valor si resulta ser true. La refactorización más simple de tu código sería esta:
function isValidAction(str: string): str is Actions {
return str in Actions;
}
possiblyInvalidEvents.forEach(event => {
if (isValidAction(event.action)) {
eventsById[event.id] = {
id: event.id,
action: event.action // no error anymore
}
}
})
Esta comprobación str in Actions realmente se basa en el hecho de que las claves y los valores del enum son idénticos, lo cual puede no ser siempre cierto. Probablemente me sentiría más cómodo comprobando los valores reales del enum y no las claves, lo cual es un poco más molesto de escribir, pero al menos es menos probable que falle de repente:
function isValidAction(str: string): str is Actions {
return (Object.keys(Actions) as Array<keyof typeof Actions>).
some(k => Actions[k] === str);
}
Pero depende de ti. Está bien, espero que eso ayude; ¡buena suerte!
Enlace de Playground al código
Respuesta #2
Solo necesitas una función de refinamiento que afirme que algún valor es Actions. Cualquier código que se ramifique basándose en este valor de retorno recordará que tu valor es de hecho el tipo que has insistido que es. Porque sabes que lo es.
function isAction(input: string): input is Actions {
return input in Actions
}
Para usarlo, simplemente llama a la función y haz la bifurcación si devuelve verdadero:
let someAction: Actions = Actions.Delete
const actionName: string = 'whatever'
if (isAction(actionName)) {
// Legal here, since we have verified the type.
someAction = actionName
}
Enlace del parque
