AnimaSola
AnimaSola

Reputation: 7846

TypeScript enum to object array

I have an enum defined this way:

export enum GoalProgressMeasurements {
    Percentage = 1,
    Numeric_Target = 2,
    Completed_Tasks = 3,
    Average_Milestone_Progress = 4,
    Not_Measured = 5
}

However, I'd like it to be represented as an object array/list from our API like below:

[{id: 1, name: 'Percentage'}, 
 {id: 2, name: 'Numeric Target'},
 {id: 3, name: 'Completed Tasks'},
 {id: 4, name: 'Average Milestone Progress'},
 {id: 5, name: 'Not Measured'}]

Is there are easy and native way to do this or do I have to build a function that casts the enum to both an int and a string, and build the objects into an array?

Upvotes: 286

Views: 537053

Answers (30)

husayt
husayt

Reputation: 15139

Here is solution which works for both numeric and string enums:

function enum2Obj<EV, ET extends { [key: string]: EV }>(e: ET) {
  const keys:(keyof ET)[] = Object.keys(e)
  const isStringEnum = isNaN(Number(keys[0]))
  const ekeys = isStringEnum ? keys : keys.slice(keys.length / 2)
  return ekeys.map((name: keyof ET) => ({ id: e[name], name }))
}


/// examples

enum Es {
  A = "a",
  B = "b",
  C = "c",
}

enum En {
  A,
  B,
  C,
}

enum Ep {
  A = 1,
  B = 2,
  C = 5,
}

console.log(enum2Obj(Es));
console.log(enum2Obj(En));
console.log(enum2Obj(Ep));

Try in TS Playground

Upvotes: 0

Cristea
Cristea

Reputation: 1077

I understand there are many answers to this question, but I have come up with a super simple solution that works for enums with values of either strings or numbers. Here’s the function I use:

enum EducationLevel {
  PRIMARY = 'primary',
  SECONDARY = 'secondary',
}

enum BooleanAnswer {
  NO = 0,
  YES = 1,
}

// Function to convert enum keys to array
const myEnumToArray = <T extends object>(myEnum: T): (keyof T)[] =>
  Object.keys(myEnum).filter(key => isNaN(Number(key))) as (keyof T)[];

console.log(myEnumToArray(EducationLevel)); // Output: ['PRIMARY', 'SECONDARY']
console.log(myEnumToArray(BooleanAnswer));  // Output: ['NO', 'YES']

Upvotes: 1

Gaurav
Gaurav

Reputation: 51

The solution below worked for me. Hope it may help someone as well -

export enum ScheduleType {
  Basic = <any>'B',
  Consolidated = <any>'C',
}

scheduleTypes = Object.keys(ScheduleType)
.filter((k, i) => i % 2)
.map((key: any) => {
  return {
    systemValue: key,
    displayValue: ScheduleType[key],
  };
});

It gave the following result - [{displayValue: "Basic", systemValue: "B"}, {displayValue: "Consolidated", systemValue: "C"}]

Upvotes: 2

TaeKwonJoe
TaeKwonJoe

Reputation: 1167

I like @Hakeem P A's answer's approach but it can be simplified quite a bit.

export enum MediaTypes {
    IMAGE = 1,
    AUDIO = 2,
    VIDEO = 3
}

To convert to an array of objects for something like binding to a select element options in Angular:

types = Object.entries(MediaTypes).filter(([key, val]) => typeof val === 'number').map(([key, val]) => ({ id: val, code: key }));

And then some form markup would look like:

<select name="media-type" [(ngModel)]="media.typeID" required>
    <option [ngValue]="null">&ndash;SELECT MEDIA TYPE&ndash;</option>
    <option [ngValue]="type.id" *ngFor="let type of types">{{type.code}}</option>
</select>

Upvotes: 4

ManojDexter
ManojDexter

Reputation: 31

I have solved it, this way. Suppose you have an enum like below

export enum UnitEnum {
  GRAM = 'gm',
  KILOGRAM = 'kg',
  LITRE = 'lt',
  CENTIMETER = 'cm',
  INCH = 'in',
  METER = 'mt',
  KILOMETER = 'km',
}

and, you have a class like this,

export interface Unit {
  Name: string;
  Symbol: string;
}

then you can create a function like below to map heterogenous enums to an object of certain type,

export function getDefaultUnits() {
  const myUnits = Object.entries(UnitEnum).map(x => {
    return { Name: x[0], Symbol: x[1] } as Unit
  })

  console.log(myUnits);

  return myUnits;
}

Upvotes: 3

يعقوب
يعقوب

Reputation: 1222

Example to get enum value inside array :

export enum DocumentationTypeEnum {
  GDPR = 'GDPR',
  HELP = 'HELP',
  OTHER = 'OTHER',
  FOOTER = 'FOOTER'
}
const keys = Object.keys(DocumentationTypeEnum);
    
console.log(keys); // Output :  ["GDPR", "HELP", "OTHER", "FOOTER"]

Upvotes: 16

soufiane ELAMMARI
soufiane ELAMMARI

Reputation: 1029

 this worked for me :

    export enum FeedBackType {
    FEEDBACK1= 'FEEDBACK1',
    FEEDBACK2= 'FEEDBACK2',
    FEEDBACK3= 'FEEDBACK3',
    }

----------------------------------------------------------------- 
    export function getTypeFeedBackList() {
    let feedbackList: FeedBackType[] = [];
    Object.keys(FeedBackType).map((key) => {
    let strEnum = key as unknown as FeedBackType;
    feedbackList.push(strEnum);
    });
    return feedbackList;
    }
---------------------------------------------------------------- 
declare this :

    public feedbackList: FeedBackType[] = [];

and after call your function in  :

    ngOnInit(): void {
    this.feedbackList = getTypeFeedBackList();
    console.log(this.feedbackList); 
    }

Happy coding ;) 

Upvotes: 0

Scott P.
Scott P.

Reputation: 1074

export function enumKeys(E: any): string[] {
    return Object.keys(E).filter(k => isNaN(Number(k)));
}

export function enumValues(E: any): string[] | number[] {
    return enumKeys(E).map(k => E[k as any]);
}

Works with both:

enum TestA {
    RED = "red",
    BLUE = "blue"
}

enum TestB {
    ONE = 1,
    TWO = 2
}

Upvotes: 3

Hakeem P A
Hakeem P A

Reputation: 19

Let the enum variable be :

 enum EnumName {
      A = 1,
      B = 2
    };

Then the list is :

const list = Object.keys(Enum)
.filter((value => isNaN(Number(value)) === false))
      .map(key => ({ id: key, value: Enum[key] }));

The value of list will be

list = [ 
{ id:1 , value: A },
{ id:2 , value: B },
];

Upvotes: 1

Max Ivanov
Max Ivanov

Reputation: 171

I would like to discourage using TS Enums in cases where list of enum entries is required.

In runtime Enum is implemented as object, but it works as you expect only in this case:

enum X {
  Z = 'z',
  F = 'f'
};

console.log(Object.values(X))
console.log(Object.keys(X))
>>>
[LOG]: ["z", "f"] 
[LOG]: ["Z", "F"] 

In this case it works with a trap (TS lets you to access value by it's numeric value):

enum X {
  Z,
  F
};

console.log(Object.values(X))
console.log(Object.keys(X))
>>>
[LOG]: ["Z", "F", 0, 1] 
[LOG]: ["0", "1", "Z", "F"] 

So any function you write to loop over Enum will work/fail according to Enum definition. Which is ... not good.

My conclusion: Enum was not designed to be used as an object. Use const instead of enum in case you need to access keys and values collections:

const Enumed = {
    X: 1,
    Y: 2
}

Typescript will control existence of object keys and you will be able to do Object.keys, etc. in safe and consistent way.

Upvotes: 3

Roman Kostetskyi
Roman Kostetskyi

Reputation: 204

Just one line:

Object.entries(GoalProgressMeasurements).map(([key, value]) => ({id: key, value: value}))

Upvotes: 9

ashiqs
ashiqs

Reputation: 111

I solved this way

        const listKeys = Object.keys(TripStatus); //TripStatus is enum type
        const numOfItem = listKeys.length/2;
        for(let i=0; i<numOfItem; i++){
          this.listStatus.push({
            id: listKeys[i],
            name: listKeys[numOfItem+i]
          })
        }

Upvotes: 0

Jaider
Jaider

Reputation: 14894

another way is

export const GoalNames = {
    [GoalProgressMeasurements.Percentage] = 'Percentage',
    [GoalProgressMeasurements.Numeric_Target] = 'Numeric Target',
    [GoalProgressMeasurements.Completed_Tasks] = 'Completed Tasks',
    [GoalProgressMeasurements.Average_Milestone_Progress] = 'Average Milestone Progress',
    [GoalProgressMeasurements.Not_Measured] = 'Not Measured'
}

and you can call:

const name = GoalNames[goalEnumVal];

Upvotes: 0

GoodTrip
GoodTrip

Reputation: 26

this method based on statement: key of enum can't be a numeric

export const isNumeric = (num?: Value | null): num is number => {
  if (num === undefined || num === null) {
    return false;
  } 
  
  const number = +num;

  if (number - number !== 0) {
    // Discard Infinity and NaN
    return false;
  }

  if (number === num) {
    return true;
  }

  if (typeof num === 'string') {
    return !(number === 0 && num.trim() === '');
  }
  return false;
};

enum En  {
  ewq1 = 1,
  we2 = 'ss',
  sad = 'sad',
}

type TEnum = {
    [id: string]: number | string;
}

export const getEnumValues = <T extends TEnum>(enumerable: T) =>
  Object.keys(enumerable)
    .filter((x) => !isNumeric(x))
    .map((key) => enumerable[key] as T[keyof T]) 

console.log(getEnumValues(En)) // [1, "ss", "sad"] 

Upvotes: 0

sjokkogutten
sjokkogutten

Reputation: 2095

Yet another approach using ES8 Object.entries

export enum Weeks {  
    MONDAY = 1,  
    TUESDAY= 2,  
    WEDNESDAY = 3,  
    THURSDAY = 4,  
    FRIDAY = 5,  
    SATURDAY=6,  
    SUNDAY=7,  
}


function convertEnumToArray(){
   const arrayObjects = []            
     // Retrieve key and values using Object.entries() method. 
     for (const [propertyKey, propertyValue] of Object.entries(Weeks)) { 

      // Ignore keys that are not numbers
      if (!Number.isNaN(Number(propertyKey))) {  
        continue;  
      }  

      // Add keys and values to array
      arrayObjects.push({ id: propertyValue, name: propertyKey });  
    }        

  console.log(arrayObjects); 
}

Will produce the following:

[ 
  { id: 1, name: 'MONDAY' },  
  { id: 2, name: 'TUESDAY' },  
  { id: 3, name: 'WEDNESDAY' },  
  { id: 4, name: 'THURSDAY' },  
  { id: 5, name: 'FRIDAY' },  
  { id: 6, name: 'SATURDAY' },  
  { id: 7, name: 'SUNDAY' } 
] 

Shamelessly stolen from this blog

Upvotes: 4

Viktor Chernodub
Viktor Chernodub

Reputation: 441

Thanks to polkovnikov.ph I was finally able to find a solution that would work for most of the use-cases.

Valid solution for the question

type Descripted<T> = {
    [K in keyof T]: {
        readonly id: T[K];
        readonly description: string;
    }
}[keyof T]

/**
 * Helper to produce an array of enum descriptors.
 * @param enumeration Enumeration object.
 * @param separatorRegex Regex that would catch the separator in your enum key.
 */
export function enumToDescriptedArray<T>(enumeration: T, separatorRegex: RegExp = /_/g): Descripted<T>[] {
    return (Object.keys(enumeration) as Array<keyof T>)
        .filter(key => isNaN(Number(key)))
        .filter(key => typeof enumeration[key] === "number" || typeof enumeration[key] === "string")
        .map(key => ({
            id: enumeration[key],
            description: String(key).replace(separatorRegex, ' '),
        }));
}

Example:


export enum GoalProgressMeasurements {
    Percentage = 1,
    Numeric_Target = 2,
    Completed_Tasks = 3,
    Average_Milestone_Progress = 4,
    Not_Measured = 5
}

console.log(enumToDescriptedArray(GoalProgressMeasurements))
// Produces:
/*
[
    {id: 1, description: "Percentage"},
    {id: 2, description: "Numeric Target"},
    {id: 3, description: "Completed Tasks"},
    {id: 4, description: "Average Milestone Progress"},
    {id: 5, description: "Not Measured"}
]
*/

Also, there's a useful util function I use to map the enumeration object to an array of available values it has:

The mapper

type NonFunctional<T> = T extends Function ? never : T;

/**
 * Helper to produce an array of enum values.
 * @param enumeration Enumeration object.
 */
export function enumToArray<T>(enumeration: T): NonFunctional<T[keyof T]>[] {
    return Object.keys(enumeration)
        .filter(key => isNaN(Number(key)))
        .map(key => enumeration[key])
        .filter(val => typeof val === "number" || typeof val === "string");
}

Working use-cases

  • Numeric enum
enum Colors1 {
    WHITE = 0,
    BLACK = 1
}
console.log(Object.values(Colors1)); // ['WHITE', 'BLACK', 0, 1]
console.log(enumToArray(Colors1));   // [0, 1]
  • String enum
enum Colors2 {
    WHITE = "white",
    BLACK = "black"
}
console.log(Object.values(Colors2)); // ['white', 'black']
console.log(enumToArray(Colors2));   // ['white', 'black']
  • Heterogenous enum
enum Colors4 {
    WHITE = "white",
    BLACK = 0
}
console.log(Object.values(Colors4)); // ["BLACK", "white", 0]
console.log(enumToArray(Colors4));   // ["white", 0]
  • Enum merged with a namespace with exported functions

enum Colors3 {
    WHITE = "white",
    BLACK = "black"
}
namespace Colors3 {
    export function fun() {}
}
console.log(Object.values(Colors3)); // ['white', 'black', Function]
console.log(enumToArray(Colors3));   // ['white', 'black']

Upvotes: 19

Ihor
Ihor

Reputation: 9

TS:

works ONLY with short (<10 elements) enum

const keys = Object.keys(Enum).filter((el: string) => el.length > 1)
console.log(keys)
  1. Object.keys() will return an array with ['0', '1', '2', 'enumElement1', 'enumElement2', enumElement3']
  2. filter() takes every element and check its length (because of string) and excludes all numbers from resulting array

Upvotes: 0

MgSam
MgSam

Reputation: 12803

I didn't like any of the above answers because none of them correctly handle the mixture of strings/numbers that can be values in TypeScript enums.

The following function follows the semantics of TypeScript enums to give a proper Map of keys to values. From there, getting an array of objects or just the keys or just the values is trivial.

/**
 * Converts the given enum to a map of the keys to the values.
 * @param enumeration The enum to convert to a map.
 */
function enumToMap(enumeration: any): Map<string, string | number> {
  const map = new Map<string, string | number>();
  for (let key in enumeration) {
      //TypeScript does not allow enum keys to be numeric
      if (!isNaN(Number(key))) continue;

      const val = enumeration[key] as string | number;

      //TypeScript does not allow enum value to be null or undefined
      if (val !== undefined && val !== null)
          map.set(key, val);
  }

  return map;
}

Example Usage:

enum Dog {
    Rover = 1,
    Lassie = "Collie",
    Fido = 3,
    Cody = "Mutt",
}

let map = enumToMap(Dog); //Map of keys to values

let objs = Array.from(map.entries()).map(m => ({id: m[1], name: m[0]})); //Objects as asked for in OP
let entries = Array.from(map.entries()); //Array of each entry
let keys = Array.from(map.keys()); //An array of keys
let values = Array.from(map.values()); //An array of values

I'll also point out that the OP is thinking of enums backwards. The "key" in the enum is technically on the left hand side and the value is on the right hand side. TypeScript allows you to repeat the values on the RHS as much as you'd like.

Upvotes: 12

Gaurav Panwar
Gaurav Panwar

Reputation: 1118

Simply this will return an array of enum values:

 Object.values(myEnum);

Upvotes: 57

Rip Ryness
Rip Ryness

Reputation: 651

function enumKeys(_enum) {
  const entries = Object.entries(_enum).filter(e => !isNaN(Number(e[0])));
  if (!entries.length) {
    // enum has string values so we can use Object.keys
    return Object.keys(_enum);
  }
  return entries.map(e => e[1]);
}

Upvotes: 1

Jai Prak
Jai Prak

Reputation: 3410

If you are using ES8

For this case only it will work perfectly fine. It will give you value array of the given enum.

enum Colors {
  WHITE = 0,
  BLACK = 1,
  BLUE = 3
}

const colorValueArray = Object.values(Colors); //[ 'WHITE', 'BLACK', 'BLUE', 0, 1, 3 ]

You will get colorValueArray like this [ 'WHITE', 'BLACK', 'BLUE', 0, 1, 3 ]. All the keys will be in first half of the array and all the values in second half.

Even this kind of enum will work fine

enum Operation {
    READ,
    WRITE,
    EXECUTE
}

But this solution will not work for Heterogeneous enums like this

enum BooleanLikeHeterogeneousEnum {
  No = 0,
  Yes = "YES",
}

Upvotes: 195

Brent
Brent

Reputation: 4876

I don't think the order can be guaranteed, otherwise it would be easy enough to slice the second half of Object.entries result and map from there.

The only (very minor) issues with the answers above is that

  • there is a lot of unnecessary type conversion between string and number.
  • the entries are iterated twice when a single iteration is just as clean and effective.
type StandardEnum = { [id: string]: number | string; [nu: number]: string;}

function enumToList<T extends StandardEnum> (enm: T) : { id: number; description: string }[] {
    return Object.entries(enm).reduce((accum, kv) => {
        if (typeof kv[1] === 'number') {
            accum.push({ id: kv[1], description: kv[0] })
        }
        return accum
    }, []) // if enum is huge, perhaps pre-allocate with new Array(entries.length / 2), however then push won't work, so tracking an index would also be required
}

Upvotes: 0

DanialModiri
DanialModiri

Reputation: 39

There is a simple solution, So when you run Object.keys(Enum) that gonna give you a Array of Values and Keys, in first slice Values and in the second one keys, so why we don't just return the second slice, this code below works for me.

enum Enum {
   ONE,
   TWO,
   THREE,
   FOUR,
   FIVE,
   SIX,
   SEVEN
}
const keys = Object.keys(Enum); 
console.log(keys.slice(keys.length / 2));

Upvotes: 1

Daniel
Daniel

Reputation: 18672

I'm surprised in a TypeScript thread no one gave valid TypeScript function with typing supported. Here's variation of @user8363 solution:

const isStringNumber = (value: string) => isNaN(Number(value)) === false;

function enumToArray<T extends {}>(givenEnum: T) {
  return (Object.keys(givenEnum).filter(isStringNumber) as (keyof T)[]).map(
    (key) => givenEnum[key]
  );
}

Upvotes: 0

Islem Maboud
Islem Maboud

Reputation: 19

Since enums with Strings values differ from the ones that have number values it is better to filter nonNumbers from @user8363 solution.

Here is how you can get values from enum either strings, numbers of mixed:

    //Helper
    export const StringIsNotNumber = value => isNaN(Number(value)) === true;
    
    // Turn enum into array
    export function enumToArray(enumme) {
      return Object.keys(enumme)
       .filter(StringIsNotNumber)
       .map(key => enumme[key]);
    }

Upvotes: 0

CMS
CMS

Reputation: 3757

I use

Object.entries(GoalProgressMeasurement).filter(e => !isNaN(e[0]as any)).map(e => ({ name: e[1], id: e[0] }));

A simple 1 line that does the job.

It does the job in 3 simple steps
- Loads the combination of keys & values using Object.entries.
- Filters out the non numbers (since typescript generates the values for reverse lookup).
- Then we map it to the array object we like.

Upvotes: 26

VaCool
VaCool

Reputation: 41

First we get an array of keys for this enum. Then, using the map () function, we convert the data to the desired format. id is obtained from the key, name is obtained from enum by the same key.

const converted = Object.keys(GoalProgressMeasurements).map(key => {
        return {
            id: GoalProgressMeasurements[key],
            name: key,
        };
    });

Upvotes: 4

nico
nico

Reputation: 21

enum GoalProgressMeasurements {
    Percentage = 1,
    Numeric_Target = 2,
    Completed_Tasks = 3,
    Average_Milestone_Progress = 4,
    Not_Measured = 5
}
    
const array = []
    
for (const [key, value] of Object.entries(GoalProgressMeasurements)) {
    if (!Number.isNaN(Number(key))) {
        continue;
    }

    array.push({ id: value, name: key.replace('_', '') });
}

console.log(array);

Upvotes: 2

Liam Kernighan
Liam Kernighan

Reputation: 2525

class EnumHelpers {

    static getNamesAndValues<T extends number>(e: any) {
        return EnumHelpers.getNames(e).map(n => ({ name: n, value: e[n] as T }));
    }

    static getNames(e: any) {
        return EnumHelpers.getObjValues(e).filter(v => typeof v === 'string') as string[];
    }

    static getValues<T extends number>(e: any) {
        return EnumHelpers.getObjValues(e).filter(v => typeof v === 'number') as T[];
    }

    static getSelectList<T extends number, U>(e: any, stringConverter: (arg: U) => string) {
        const selectList = new Map<T, string>();
        this.getValues(e).forEach(val => selectList.set(val as T, stringConverter(val as unknown as U)));
        return selectList;
    }

    static getSelectListAsArray<T extends number, U>(e: any, stringConverter: (arg: U) => string) {
        return Array.from(this.getSelectList(e, stringConverter), value => ({ value: value[0] as T, presentation: value[1] }));
    }

    private static getObjValues(e: any): (number | string)[] {
        return Object.keys(e).map(k => e[k]);
    }
}

Upvotes: 18

Boban Stojanovski
Boban Stojanovski

Reputation: 41

You can do that in this way:

export enum GoalProgressMeasurements {
    Percentage = 1,
    Numeric_Target = 2,
    Completed_Tasks = 3,
    Average_Milestone_Progress = 4,
    Not_Measured = 5
}

export class GoalProgressMeasurement {
    constructor(public goalProgressMeasurement: GoalProgressMeasurements, public name: string) {
    }
}

export var goalProgressMeasurements: { [key: number]: GoalProgressMeasurement } = {
    1: new GoalProgressMeasurement(GoalProgressMeasurements.Percentage, "Percentage"),
    2: new GoalProgressMeasurement(GoalProgressMeasurements.Numeric_Target, "Numeric Target"),
    3: new GoalProgressMeasurement(GoalProgressMeasurements.Completed_Tasks, "Completed Tasks"),
    4: new GoalProgressMeasurement(GoalProgressMeasurements.Average_Milestone_Progress, "Average Milestone Progress"),
    5: new GoalProgressMeasurement(GoalProgressMeasurements.Not_Measured, "Not Measured"),
}

And you can use it like this:

var gpm: GoalProgressMeasurement = goalProgressMeasurements[GoalProgressMeasurements.Percentage];
var gpmName: string = gpm.name;

var myProgressId: number = 1; // the value can come out of drop down selected value or from back-end , so you can imagine the way of using
var gpm2: GoalProgressMeasurement = goalProgressMeasurements[myProgressId];
var gpmName: string = gpm.name;

You can extend the GoalProgressMeasurement with additional properties of the object as you need. I'm using this approach for every enumeration that should be an object containing more then a value.

Upvotes: 0

Related Questions