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?
This question is related to
javascript
arrays
typescript
enums
casting
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));
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",
}
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
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, ' ') }))
}
Simply this will return an array of enum values:
Object.values(myEnum);
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.
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]);
}
}
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.
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,
};
});
Here's the simple function with correct typing I use
/**
* Helper to produce an array of enum values.
* @param enumeration Enumeration object.
*/
export function enumToArray<T, G extends keyof T = keyof T>(enumeration: T): T[G][] {
// tslint:disable: comment-format
// enum Colors {
// WHITE = 0,
// BLACK = 1,
// }
// Object.values(Colors) will produce ['WHITE', 'BLACK', 0, 1]
// So, simply slice the second half
const enumValues = Object.values(enumeration);
return enumValues.slice(enumValues.length / 2, enumValues.length) as T[G][];
}
Usage example:
enum Colors {
Red = 1,
Blue = 2,
}
enumToArray(Colors)
enum GoalProgressMeasurements {_x000D_
Percentage = 1,_x000D_
Numeric_Target = 2,_x000D_
Completed_Tasks = 3,_x000D_
Average_Milestone_Progress = 4,_x000D_
Not_Measured = 5_x000D_
}_x000D_
_x000D_
const array = []_x000D_
_x000D_
for (const [key, value] of Object.entries(GoalProgressMeasurements)) {_x000D_
if (!Number.isNaN(Number(key))) {_x000D_
continue;_x000D_
}_x000D_
_x000D_
array.push({ id: value, name: key.replace('_', '') });_x000D_
}_x000D_
_x000D_
console.log(array);
_x000D_
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));
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]);
}
_x000D_
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.
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
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
}
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]);
}
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]
);
}
Source: Stackoverflow.com