'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[]

Playground

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