-
Notifications
You must be signed in to change notification settings - Fork 12.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Suggestion: Iterate through Enum values #4753
Comments
I don't think this is a common enough case to emit such a helper for all enums that every TypeScript user writes. Note that you can merge modules and enums to add methods to the enum yourself if you really do need this functionality. For example, using ES6 generators you can achieve something close to what you want: enum E {
A = 2,
B = 4,
C = 6
}
module E {
export function* getvalues() {
yield E.A;
yield E.B;
yield E.C;
}
}
for(var i of E.getvalues()) {
console.log(i);
} |
Okay, thanks for the suggestion. I'll see if that is a suitable solution. |
Seems like it would be a common use-case to me. I certainly would like that included automatically. |
It is a common case, for example, you may want to generate a dropdown list from enum dynamically. It is easy to implement too, just add length property to the enum. (function (MyOptions) {
MyOptions[MyOptions["option1"] = 0] = "option1";
MyOptions[MyOptions["option2"] = 1] = "option2";
MyOptions.length = 2;
})(MyOptions || (MyOptions = {})); |
I don't understand the argumentation to not include this at all. Too complex maybe, but not a common use case... What other language has enums and no way to iterate over them? |
You can iterate over them. See http://stackoverflow.com/a/18112474/1704166 . |
Thank you that does work. My mistake - I misunderstood this issue. |
@danquirk Can't compiler just emit the same code? It is really sad it's not supported, as every other language with enums have the functionality |
Please don't show up with noise comments. |
Well, generally how else are people able to show their support but by making some noise for the cause? This issue is rank 4 on DuckDuckGo and 3 on Google when searching "typescript iterate enum" (which is how I got here as well). I guess wanting to iterate over enums is not an uncommon use case after all and both solutions that were brought up in here are essentially workarounds and quite bulky. |
TypeScript isn't going to emit additional code for every enum just to enable you to do something you can already do by writing code in userland. All the data you need is present at runtime; there's no need for it. |
I guess by going down that thought we should all be writing and compiling our own assembly code since everything is technically already possible there? /j I think asking "Can it work without the new feature?" is the wrong approach. The main purpose of a programming language is to make it easy for people to produce solutions. If this new feature accomplishes this, is intuitive (Because let's face it: Both solutions are not unless you are knee deep in the specs) and comes at a reasonable cost: Why not? There are many examples of "sugar" in JS, basically the whole Array methods are. So forgive me for asking but why is it such a big deal for TS to add aditional functionality? I'm guessing the third part of my before mentioned feature requirements is the problem? |
I want to add one more technical point. One suggested approach is http://stackoverflow.com/a/18112474/1704166. The problem with it is that it doesn't iterate over values in a type-safe manner - it will produce numbers or strings. So downstream of iteration type safety is lost. I believe a lot of people who need the iteration are lead to that approach and that's bad. @danquirk's solution is fine and I want to give credit to it. As for philosophical debate, I think a lot of people who are coming to Typescript from other languages, while in awe of other language features, are left wondering about enum support. Enums in Typescript are not on par with other high-level languages. Personally I don't care what it is compiled down into, as long as it behaves like I expect from an enum type. Maybe it should be reassessed if the right compromise was made between enum usefulness and complexity of the generated code. |
Edit: This solution works for my use case: It is an evil hack. But it does work generically. I tested it with a numeric/integral enum. Note: it does not compile in JSFiddle, which does not recognize the For easy and useful approaches, that haven't managed to bog down runtimes, have a look at Java: https://docs.oracle.com/javase/tutorial/java/javaOO/enum.html. Or Python: https://docs.python.org/3/library/enum.html. To me, there is nothing philosophical about current support for enum iteration in Typescript. It is just broken. It doubles the number enum values, present each one twice: once for the name, and again for the value. There's no clear cut way to distinguish between names and values. Nor any guarantee of order in the language (could break with any TS update). There's no philosophy there to defend. Just a misunderstanding of what enumerated types mean in any scripting language (e.g. Java, Python, ...). A practical issue I am having is how to deserialize values from the server (via JSON) into actual enum values in my Typescript code. Some evil hack may work yet. But it really should be easy to iterate through enum instances, compare the given value to either the name or value and, if I have a match, use the current enum instance. Currently, this direct approach is not possible.
|
Not so common use case (?): enum QueueEvents {
ITEM_PUSHED = 'item_pushed',
ITEM_POPPED = 'item_popped'
} class Queue {
private readonly eventEmitter: EventEmitter = new EventEmitter();
// some linked list implementation here
on(event: QueueEvents, listener: (...args: any[]) => void) {
this.eventEmitter.on(event, listener);
}
} Now let's iterate the QueueEvents (in some unit test): const queue = new Queue();
for (let event in QueueEvents) {
queue.on(event, () => console.log('emitted ' + event));
} Doesn't work since |
example here: function enumToArray<T>(t: T): ReadonlyArray<T[keyof T]> {
const values = Object.values(t);
return values.some(x => typeof x === 'number')
? values.filter(x => typeof x === 'number')
: values;
}
enum PlayType {
tragedy = 1,
comedy = 2,
}
/**
* @type readonly PlayType[]
* @value [1, 2]
*/
const PlayTypeList = enumToArray(PlayType);
enum PlayType2 {
tragedy = 'tragedy',
comedy = 'comedy',
}
/**
* @type readonly PlayType2[]
* @value ['tragedy', 'comedy']
*/
const PlayTypeList2 = enumToArray(PlayType2); |
Doesn't work since members of the array are strings or numbers here, not enums. For example, you can't pass them to a function expecting an array of enum values. For something that does work, see: |
Update: function enumEntries<T>(t: T): ReadonlyArray<readonly [keyof T, T[keyof T]]> {
const entries = Object.entries(t);
const plainStringEnum = entries.every(
([key, value]) => typeof value === 'string'
);
return (plainStringEnum
? entries
: entries.filter(([k, v]) => typeof v !== 'string')) as any;
}
function enumKeys<T>(t: T): ReadonlyArray<keyof T> {
return enumEntries(t).map(([key]) => key);
}
function enumValues<T>(t: T): ReadonlyArray<T[keyof T]> {
const values = Object.values(t);
const plainStringEnum = values.every(x => typeof x === 'string');
return plainStringEnum ? values : values.filter(x => typeof x !== 'string');
} |
can enum with number values compile like this :
i have a babel plugin test it babel-plugin-iterable-enum |
@houfeng0923 I like the looks of that. Nice idea pushing the reverse mapping into the |
Is there a reason not to use the Building upon the previous solutions, I came up with something along the lines of the following code. function iterableEnum<T>(t: T): {[key: string]: T[keyof T]} {
const keys = Object.keys(t) as [keyof T]
return keys.reduce((agg, key) => Object.defineProperty(agg, key, {
value: t[key],
enumerable: !isFinite(+key)
}), {})
} |
I figured I might as well share what I did in order to iterate over enums. This is probably not the most efficient or elegant way to do this, but it yields values with the correct type, works with both numeric and string enums, is pretty short/straightforward and works for me: function enumValues<T extends string>(enumObj: { [ key: string ]: T }): IterableIterator<T>;
function enumValues<T extends string | number>(enumObj: { [ key: string ]: T }): IterableIterator<Exclude<T, string>>;
function* enumValues<T>(enumObj: { [ key: string ]: T }): IterableIterator<T> {
let isStringEnum = true;
for (const property in enumObj) {
if (typeof enumObj[property] === 'number') {
isStringEnum = false;
break;
}
}
for (const property in enumObj) {
if (isStringEnum || typeof enumObj[property] === 'number') {
yield enumObj[property];
}
}
}
// use like this; string enums should work too
enum MyEnum { foo, bar, baz }
for (const value of enumValues(MyEnum)) {
console.info(value); // value is of type MyEnum
} |
Enums currently get outputted to an object with two-way mapping of numbers. This makes it hard to iterate through the names (or values) of an enum. A method on the enum object to get the keys of the enum would allow iterating.
See this StackOverflow question for a use case of iterating. (And use of a regular expression for filtering).
Possible Solutions:
Add a length property (for enums with default index)too limitedComplications:
The text was updated successfully, but these errors were encountered: