1

I have an object declared like that:

type MyType = { [key in MyEnum]?: number}

MyEnum has that form:

enum MyEnum {
  Foo = 1, Bar = 2
}

I want to loop over my object key/value pairs, and get back to the enum value:

const myObject: MyType = {...}
for (const myKey in myObject) {
  // myKey is of type string, not "MyEnum". How to get back to it?
}

Is there any way to do that?

3
  • 1
    Please make your code complete. Provide a definition for MyEnum and for myObject. Commented Jul 30, 2021 at 21:34
  • MyType comes out as { 0?: number; 1?: number; 2?: number }. What you intended? It doesn't look like it. Commented Jul 30, 2021 at 21:35
  • I complete the exemple @spender Commented Jul 30, 2021 at 21:40

2 Answers 2

2

This seemingly surprising issue is the consequence of duck typing in Typescript and to a lesser extent how objects work in Javascript.

The myKey is typed as a string, because of two reasons. Firstly, Typescript cannot guarantee that an object will only contain properties of MyType. Duck typing allows you to use objects that have the same and more properties as MyType on places where MyType is requested. This is a feature of Typescript, but in this case a handicap.

Example of duck typing

// Types
enum MyEnum {
  Foo = 1, Bar = 2
};

type MyType = { [key in MyEnum]?: number}

// Duck typing example
const normalObject: MyType = { [MyEnum.Foo]: 3, [MyEnum.Bar]: 4 };
const duckTypedObject = { [MyEnum.Foo]: 5, "extraProperty": 10};

function myFunction(obj: MyType){
  for (const myKey in obj) {
    console.log(myKey);
  }
}

myFunction(normalObject);
// This will print out "extraProperty" as well.
myFunction(duckTypedObject);

Second, because properties on objects in Javascript can only be symbols or strings other types are coerced into strings. Since the for loop will only iterate over the string properties of an object, Typescript chooses string as the most specific type for myKey.

Normally you could still type myKey by declaring a variable outside the loop construct. In this case that does not work, because you are using a numeric enum. You would need to parse the strings in order to get the numbers again and cast it back into an enum. I would highly advise against this. You could however use a string enum, but this would still expose you to errors if your colleagues would use duck typing.

String Enum Solution

enum MyStringEnum {
  Foo = "FOO", Bar = "BAR"
}

type MyType2 = { [key in MyStringEnum]?: number};
const obj: MyType2 = { [MyStringEnum.Foo]: 3};

let myKey: MyStringEnum;
for (const myKey in obj){
  console.log(myKey);
}

My suggestion would be to use the Map type from Typescript. As you can see the key is typed without any additional hacks and you evade issues with regard to duck typing.

enum MyEnum {
  Foo = 1, Bar = 2
};

let myMap = new Map<MyEnum, number>([
  [MyEnum.Foo, 3],
  [MyEnum.Bar, 4]
]); 

for (const [key] of myMap){
  console.log(key);
}

Here is a link to the executable code snippet.

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks! I am serializing and deserializing this objet in MongoDB and through network, so the Map type is not a viable option for me unfortunately.
@RomainF. Which library are you using to serialize it? I would then suggest to maybe look into a string enum if this is a possibility. Are there any specific reasons to use a numeric enum?
I am not using any library for serialization, only JSON parse and stringify (and MongoDB raw data on database side). Because I use raw data on database side, using numeric enum saves space. The project is here: game-park.com. Each board game has a custom data structure, but can be adapted without worrying about the databases or data transfers. Awesome gain of time.
1
enum MyEnum {
  Foo = 1, Bar = 2
}

type MyType = { [key in MyEnum]?: number}


declare var myObject: MyType;

for (const myKey in myObject) {
  // myKey is of type string, not "MyEnum". How to get back to it?
}

This made by design. Please be aware that Object.keys(MyEnum) will also return string[] instead of Array<keyof typeof MyEnum>.

In order to do that, you need to use type assertion - as operator.

enum MyEnum {
    Foo = 1, Bar = 2
}

type MyType = Record<keyof typeof MyEnum, 1>


declare var myObject: typeof MyEnum;


const loop =
    (Object.keys(MyEnum) as Array<keyof typeof MyEnum>)
        .forEach(elem => { // elem is Foo | Bar

        })

Playground

UPDATE

enum MyEnum {
    Foo = 1, Bar = 2
}

type MyType = Record<keyof typeof MyEnum, 1>


declare var myObject: typeof MyEnum;

type Values<T> = T[keyof T]
type O = {
    [Prop in Values<typeof MyEnum>]: Prop
}

const loop =
    (Object.values(MyEnum) as Array<Values<typeof MyEnum>>)
        .forEach((elem /** 1 | 2 */) => { })

2 Comments

For me with your example, elem is "Foo" | "Bar", the enum keys as strings, not the enum values. I am trying to use "parseInt" right now, as my enum values are integers only.
You seem to be looping over the keys of the enum itself, rather than an object whose keys are typed by the enum.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.