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.
MyEnumand formyObject.MyTypecomes out as{ 0?: number; 1?: number; 2?: number }. What you intended? It doesn't look like it.