Search code examples
typescriptenumsoptional-parametersfunction-definitiongeneric-type-parameters

Typescript Return the enum values in parameter using a generic enum type method


I need to return a list with all values of an Enum (specified as parameter or generic). If the user has a specific role I need only some of the Enum values given as parameter.

I considered to write the two parameters as a tuple because they are either given both or none.
My first try would look like this:

getItemsBasedOnRole<T extends enum>(...[role, items]?: [UserRole, T[]]) : string[]
{
    const allValues = Object.values(T);
    if (role && items) // tuple parameter exists
        if (this.securityService.getUser.role === role)
            return items;

    return allValues;
}

const result = getItemsBasedOnRole<MyEnum>([ClientRole], [MyEnum.Value1, MyEnum.Value3]);
// returns [MyEnum.Value1, MyEnum.Value3]

const result = getItemsBasedOnRole<MyEnum>();
// returns [MyEnum.Value1, MyEnum.Value2, MyEnum.Value3]


export enum MyEnum{
    Value1 = 'Value1',
    Value2 = 'Value2',
    Value3 = 'Value3',
}

The problems are in Typescript:

  • T extends enum does not exist.
    Solution: Replaced with T extends { [s: number]: string }
  • Object.values(T) needs a value and T is a type.
    Considered adding a parameter enumValue: T but calling the method I could not pass MyEnum, it was requiring MyEnum.ValueX.
    No solution found for this.
  • [role, items]? optional tuple does not exist.
    No solution found for this.

My last attempt, which shows the errors in the above list:

getItemsBasedOnRole<T extends { [s: number]: string }>(enumValue: T, ...[role, items]: [UserRole, T[]]): string[]

The above problems are related to types and function definition and I just want to return a dynamic list. I am open to any ideas: multiple functions, using parameter instead of generic type, etc.


Solution

  • I would suggest using constant objects instead of enums, since they are more predictable and easier to manipulate. We will need to apply const assertion to prevent the compiler from widening the type of the object and to make sure that the object is type-safe, we are going to use satisfies operator:

    export const MyEnum = {
      Value1: 'Value1',
      Value2: 'Value2',
      Value3: 'Value3',
    } as const satisfies Record<string, string>
    

    Since, our enum is Record<string, string> we will change the constraint of the generic argument to the relevant one:

    declare function getItemsBasedOnRole<T extends Record<string, string>>(obj: T, ...args:[something]) {}
    

    To get the values of the whole object, we are going to use indexed access: T[keyof T] will give the union of all values:

    declare function getItemsBasedOnRole<T extends Record<string, string>>(obj: T, ...args: [role?: UserRole, values?: T[keyof T][]]): string[]
    

    Usage:

    getItemsBasedOnRole(MyEnum, 'role', ['Value1', 'Value2'])
    

    playground