Search code examples
typescriptenums

How to get all Enum Flags from a Number in TypeScript?


Given:
Enum: {1, 4, 16}
Flags: 20

When: I pass Flags to a function

Then: I get array of flags of given Enum: [4, 16]

Note: I have tried to convert Enum to array manually and treat values as numbers. But I get overwhelmed when it gets to TS and I want to make that function dynamic in case Enum gets changed. I really don't want hardcode that. Please help!

enum Enum {
    one = 1,
    four = 4,
    sixteen = 16,
}

const Flags: number = 20;

function getEnumValues(flags: number): Enum[] {
    const enumFlags = [1, 4, 16];
    
    const enums: Enum[] = [];

    enumFlags.forEach(ef => {
        if ((ef & flags) != 0) {
            enums.push(ef);
        }
    })

    return enums;
}

console.log(getEnumValues(Flags));

Solution found here: Typescript flagged enum get values Is not good because my Enum is not a sequence 2 and 8 is missing.


Solution

  • I have encountered a similar challenge in the past. And by coincidence, I have written an article about that, because it was a really interesting challenge.

    The first thing you need is a helper that extracts Enum's flags as an array by treating TS enum as an ordinary object. After that, your approach is good to go by looping over flags and see if it's present in your number.

    Snippet copied from the link above's code sandbox.

    const isPowerOfTwo = (x: number): boolean => {
      return x != 0 && (x & (x - 1)) == 0;
    };
    
    export function getEnumFlags<
      O extends object,
      K extends O[keyof O] = O[keyof O]
    >(obj: O): K[] {
      const isFlag = (arg: string | number | K): arg is K => {
        const nArg = Number(arg);
        const isNumber = !Number.isNaN(nArg);
        return isNumber && isPowerOfTwo(nArg);
      };
    
      const enumFlags: K[] = [];
    
      Object.keys(obj).forEach(key => {
        const nKey = Number(key);
        if (isFlag(nKey)) {
          enumFlags.push(nKey);
        }
      });
    
      return enumFlags;
    }
    

    Edit 2024.06.12: My answer is handling edgecases when enums have intermediary values used for convenience. E.g.: enum MyEnum { first = 1, second = 2, both = 3 } - both is NOT a flag its a convenience instead of writing first | second.