Generic enum type guard

I didn't find such a package on npm and I thought it would be nice to create one: https://www.npmjs.com/package/is-some-enum. I think it will help someone :)


TS string enums and number enums have very different JS emits.

The accepted answer works for OP's case of string enums.

But someone using number enums may naively think that it will also work for their use case. Be careful.

//number enum here
enum E {
  A,
  B,
  C,
}

const isSomeEnum = <T>(e: T) => (token: any): token is T[keyof T] =>
  (Object as any).values(e).includes(token as T[keyof T]);

console.log(isSomeEnum(E)("A")); //expected false, actual true
console.log(isSomeEnum(E)(0));   //expected true , actual true

function isSomeEnum2<T> (e: T) : (token: unknown) => token is T[keyof T] {
  const keys = Object.keys(e)
    .filter((k) => {
      return !/^\d/.test(k);
    });
  const values = keys.map((k) => {
    return (e as any)[k];
  });
  return (token: unknown): token is T[keyof T] => {
    return values.includes(token);
  };
};

console.log(isSomeEnum2(E)("A")); //expected false, actual false
console.log(isSomeEnum2(E)(0));   //expected true , actual true

Playground


A variation on top of @jcalz answer is just a single and plain function that can be directly used:

const isEnumValue = <T extends { [k: string]: string }>(something: any, enumObject: T): something is T[keyof T] =>
    typeof something === 'string' && Object.values(enumObject).includes(something);

As Justin AnyhowStep observed, this function works only on string enums, sot I put the T extends { [k: string]: string } clause. In this way we havethis behaviour:

enum StringEnum {
    A = 'aaa',
    B = 'bbb',
}

enum NumberEnum {
    A,
    B,
}

let a;

if (isEnumValue(a, StringEnum)) {
    if (a === 'SOMETHING') {
        // compiler complains:
        // This condition will always return 'false' since the types 'StringEnum' and '"SOMETHING"' have no overlap.
    }
}
if (isEnumValue(a, NumberEnum)) {
    // compiler complains:
    // Argument of type 'typeof NumberEnum' is not assignable to parameter of type '{ [k: string]: string; }'.
    // Property 'A' is incompatible with index signature.
    // Type 'NumberEnum' is not assignable to type 'string'.
}


You mean like this?

const isSomeEnum = <T>(e: T) => (token: any): token is T[keyof T] =>
    Object.values(e).includes(token as T[keyof T]);

SoisSomeEnum produces type guard functions from enum objects. The type T[keyof T] means the types of the property values of T.

const isMyEnum = isSomeEnum(MyEnum);
// const isMyEnum: (token: any) => token is MyEnum

When you call isSomeEnum(MyEnum), the type T is inferred as typeof MyEnum, and then T[keyof T] is the property values of that, which is MyEnum.

Hope that helps. Good luck!

Link to code

Tags:

Typescript