Skip to content

Suggestion: Iterate through Enum values #4753

Closed

Description

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 limited
  • Function on enum type filtering keys looking for numbers (could break with other index types)
  • Add a function with the keys hard coded when compiling

Complications:

Activity

  1. danquirk commented on Sep 11, 2015

    @danquirk
    Member

    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);
    }
  2. added
    SuggestionAn idea for TypeScript
    Too ComplexAn issue which adding support for may be too complex for the value it adds
    DeclinedThe issue was declined as something which matches the TypeScript vision
    on Sep 11, 2015
  3. aciccarello commented on Sep 14, 2015

    @aciccarello
    Author

    Okay, thanks for the suggestion. I'll see if that is a suitable solution.

  4. Novalaina commented on Jan 3, 2016

    @Novalaina

    Seems like it would be a common use-case to me. I certainly would like that included automatically.

  5. bladepan commented on Feb 23, 2016

    @bladepan

    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 = {}));
  6. npiv commented on May 19, 2016

    @npiv

    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?

  7. RyanCavanaugh commented on May 19, 2016

    @RyanCavanaugh
    Member

    no way to iterate over them?

    You can iterate over them. See http://stackoverflow.com/a/18112474/1704166 .

  8. npiv commented on May 21, 2016

    @npiv

    Ryan Cavanaugh (@RyanCavanaugh)

    Thank you that does work. My mistake - I misunderstood this issue.

  9. alamothe commented on May 3, 2018

    @alamothe

    Dan Quirk (@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

  10. omerman commented on Jun 6, 2018

    @omerman

    Dan Quirk (@danquirk) I agree with alamothe so much that I'm leaving this comment saying just that.

  11. RyanCavanaugh commented on Jun 6, 2018

    @RyanCavanaugh
    Member

    Please don't show up with noise comments.

  12. saenglert commented on Nov 26, 2018

    @saenglert

    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.

  13. RyanCavanaugh commented on Nov 26, 2018

    @RyanCavanaugh
    Member

    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.

  14. saenglert commented on Nov 26, 2018

    @saenglert

    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?

  15. alamothe commented on Nov 26, 2018

    @alamothe

    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. Dan Quirk (@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.

  16. CharlieReitzel commented on Oct 11, 2019

    @CharlieReitzel

    Edit: This solution works for my use case:
    https://stackoverflow.com/questions/52370544/parse-string-as-typescript-enum

    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 keyof operator. But it works with current Typescript in my Angular 8 code and Jasmine unit test. Hth.


    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.

    enum Planet {
        MARS(1),
        VENUS(2),
        EARTH(3),
        MARS(4), ...
        NEPTUNE(8);
    
        int pno;
        Planet( int pno ) {
            this.pno = pno;
        }
        
        public static Planet findPlanetByNumber( int planetNum ) {
            for (Planet p : Planet.values()) {
                if ( planetNum == p.pno ) {
                    return p;
                }
            }
            return null;
        }
    }
    
  17. newlukai commented on Oct 24, 2019

    @newlukai

    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 event is a string here, not a QueueEvents.

  18. xiaoxiangmoe commented on Nov 29, 2019

    @xiaoxiangmoe
    Contributor

    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);
  19. CharlieReitzel commented on Nov 29, 2019

    @CharlieReitzel

    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;
    }
    [ ... snip ... ]
    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:
    https://stackoverflow.com/questions/52370544/parse-string-as-typescript-enum.

  20. xiaoxiangmoe commented on Nov 30, 2019

    @xiaoxiangmoe
    Contributor

    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');
    }
  21. houfeng0923 commented on Jan 9, 2020

    @houfeng0923

    can enum with number values compile like this :

    const Direction = {
      Left: 0,
      Right: 1,
      Down: 2,
      Up: 3,
      '__proto__': {
        0: 'Left',
        1: 'Right',
        2: 'Down',
        3: 'Up'
      }
    };
    

    i have a babel plugin test it babel-plugin-iterable-enum

  22. CharlieReitzel commented on Jan 10, 2020

    @CharlieReitzel

    houfeng (@houfeng0923) I like the looks of that. Nice idea pushing the reverse mapping into the __proto__ space.

  23. etnbrd commented on Jan 28, 2020

    @etnbrd

    Is there a reason not to use the enumerable property attribute?
    It could only allow the keys of the enum to appear during a for loop or with Object.keys, and hiding the values.
    While keeping the forth and back translation between keys and values, as is already the case.

    Building upon the previous solutions, I came up with something along the lines of the following code.
    for numeric enum only

    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)
        }), {})
    }
  24. Ticmea commented on Sep 17, 2020

    @Ticmea

    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
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    DeclinedThe issue was declined as something which matches the TypeScript visionSuggestionAn idea for TypeScriptToo ComplexAn issue which adding support for may be too complex for the value it adds

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions