'Filter out array values that are not in a literal union type
For example, I have a literal string union type:
type AllowedColor = 'red' | 'blue';
And I'm receiving the color from the server.
let colors = getColorsFromServer();
I want to filter the array to only consist of AllowedColor type. This is a pseudo code that is not working:
colors.filter(color => color is AllowedColor);
Is there a way to do this?
Solution 1:[1]
Type aliases don't exist at runtime
You can't use a type alias in any runtime logic because of type erasure. type AllowedColor will not exist in the generated Javascript.[1] Typescript types have only one role: static type checking logic.
You have to approach it from the other direction
type derived from a const array
Instead of runtime logic or data depending on static type information, have static type information depend on runtime data:
// values available at runtime
const allowedColors = ['red', 'blue', 'green'] as const
// type available at compile time, equivalent to:
// type AllowedColor = 'red' | 'blue' | 'green'
type AllowedColor = typeof allowedColors[number]
defining a Typescript type guard
You could then define a convenient isAllowedColor validation function that serves double-duty as a Typescript type guard:
function isAllowedColor(color:string): color is AllowedColor {
return (allowedColors as unknown as string[]).includes(color)
}
type derived from an enum
If an allowedColors enum would be more useful than the array above:
// values available at runtime
enum allowedColors {red = 'red', blue = 'blue', green = 'green'}
// type available at compile time, equivalent to
// type AllowedColor = 'red' | 'blue' | 'green'
type AllowedColor = typeof allowedColors[keyof typeof allowedColors]
// a validation function that also acts as a Typescript type guard
function isAllowedColor(color:string): color is AllowedColor {
return color in allowedColors
}
usage
let someColors = ['red', 'blue', 'chartreuse', 'teal', 'ocher']
// because isAllowedColor is also a type guard, we are using the version
// of Array<T>.filter that accepts a type guard predicate, with a return
// type of T[], in this case AllowedColor[]
let filteredColors: AllowedColor[] = someColors.filter(isAllowedColor)
console.log(filteredColors) // [ 'red', 'blue' ]
Solution 2:[2]
For now, my workaround for this is:
const allowedColors = ['red', 'blue'] as const;
type AllowedColor = typeof allowedColors[number];
let colors = getColorsFromServer();
colors.filter(color => allowedColors.includes(color));
Let me know if you know better approach
Solution 3:[3]
It worth using typeguard, since Array.prototype.filter expects a predicate as an argument.
Consider this example:
type AllowedColor = 'red' | 'blue';
const getColorsFromServer = () => {
let color: string[] = ['green', 'red', 'blue']
return color;
}
let colors = getColorsFromServer();
const isAllowed = (color: string): color is AllowedColor => /red|blue/.test(color)
// const isAllowed = (color: string): color is AllowedColor => ['red', 'blue'].includes(color)
const result = colors.filter(isAllowed) // AllowedColor[]
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | Stucky |
| Solution 3 | captain-yossarian from Ukraine |
