'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?
Solution 1:[1]
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",
}
Solution 2:[2]
A tricky bit is that TypeScript will 'double' map the enum in the emitted object, so it can be accessed both by key and value.
enum MyEnum {
Part1 = 0,
Part2 = 1
}
will be emitted as
{
Part1: 0,
Part2: 1,
0: 'Part1',
1: 'Part2'
}
So you should filter the object first before mapping. So @Diullei 's solution has the right answer. Here is my implementation:
// Helper
const StringIsNumber = value => isNaN(Number(value)) === false;
// Turn enum into array
function ToArray(enumme) {
return Object.keys(enumme)
.filter(StringIsNumber)
.map(key => enumme[key]);
}
Use it like this:
export enum GoalProgressMeasurements {
Percentage,
Numeric_Target,
Completed_Tasks,
Average_Milestone_Progress,
Not_Measured
}
console.log(ToArray(GoalProgressMeasurements));
Solution 3:[3]
Enums are real objects that exist at runtime. So you are able to reverse the mapping doing something like this:
let value = GoalProgressMeasurements.Not_Measured;
console.log(GoalProgressMeasurements[value]);
// => Not_Measured
Based on that you can use the following code:
export enum GoalProgressMeasurements {
Percentage = 1,
Numeric_Target = 2,
Completed_Tasks = 3,
Average_Milestone_Progress = 4,
Not_Measured = 5
}
let map: {id: number; name: string}[] = [];
for(var n in GoalProgressMeasurements) {
if (typeof GoalProgressMeasurements[n] === 'number') {
map.push({id: <any>GoalProgressMeasurements[n], name: n});
}
}
console.log(map);
Reference: https://www.typescriptlang.org/docs/handbook/enums.html
Solution 4:[4]
Simply this will return an array of enum values:
Object.values(myEnum);
Solution 5:[5]
Easy Solution. You can use the following function to convert your Enum to an array of objects.
buildGoalProgressMeasurementsArray(): Object[] {
return Object.keys(GoalProgressMeasurements)
.map(key => ({ id: GoalProgressMeasurements[key], name: key }))
}
If you needed to strip that underscore off, we could use regex as follows:
buildGoalProgressMeasurementsArray(): Object[] {
return Object.keys(GoalProgressMeasurements)
.map(key => ({ id: GoalProgressMeasurements[key], name: key.replace(/_/g, ' ') }))
}
Solution 6:[6]
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]);
}
}
Solution 7:[7]
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.
Solution 8:[8]
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']
Solution 9:[9]
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.
Solution 10:[10]
Just one line:
Object.entries(GoalProgressMeasurements).map(([key, value]) => ({id: key, value: value}))
Solution 11:[11]
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,
};
});
Solution 12:[12]
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
Solution 13:[13]
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);
Solution 14:[14]
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));
Solution 15:[15]
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]);
}
Solution 16:[16]
Exemple 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"]
Solution 17:[17]
TS:
works ONLY with short (<10 elements) enum
const keys = Object.keys(Enum).filter((el: string) => el.length > 1)
console.log(keys)
- Object.keys() will return an array with ['0', '1', '2', 'enumElement1', 'enumElement2', enumElement3']
- filter() takes every element and check its length (because of string) and excludes all numbers from resulting array
Solution 18:[18]
I know typescript from just a few months, and 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"}]
Solution 19:[19]
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.
Solution 20:[20]
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]);
}
Solution 21:[21]
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]
);
}
Solution 22:[22]
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
}
Solution 23:[23]
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"]
Solution 24:[24]
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];
Solution 25:[25]
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]
})
}
Solution 26:[26]
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.
Solution 27:[27]
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 },
];
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow