I hope this is not a silly question. Why when I use for (let property in myObject)... property is recognized by typescript as "string" instead of "keyof typeof myObject"? Is there a way or setting to use the for...in to iterate over an object in a typesafe way?
Important: this question is very similar to this one, I understand if it might be considered a duplicate. I decided to post a new question because the question is quite old, and it does not addresses the case of a constant object or with a defined type declared as const. Many of its answers address the usage of variable objects or arrays, and only one (the most recent one) addresses iterating over an object like I need. But I don't know how his answer, compared to the alternatives I was trying to implement compare, so I wanted to focus on this specific use case.
For example, this is perfectly valid code in Javascript
let myObject = {
a: "propA",
b: "propB"
}
for (let prop in myObject){
console.log(`${prop}: ${myObject[prop]}`);
// prints "a: propA", "b: propB"
}
But in typescript its marked as invalid, and throws the following error: "Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ a: string; b: string; }'."
I don't understand why. As I understand it, prop should be typed as "a" | "b" because the for...in iterates over the properties of an object, so the type system should expect no other values going there.
I read this SO post which explain that because the object accessor can be pretty much anything, then typescript types it as string, but if I have a constant, or declare it as const I expect the properties to be inferred, yet it makes no difference. I don't know if I am missing something
That post also has one answer that states that to use for...in you can use Object.prototype.hasOwnProperty.call(obj, key) to validate if the property exists, but I don't know if this is the best way and also it looks kind of redundant.
I tried declaring a type beforehand as type myObjectProps = keyof typeof myObject; but I found that "The left-hand side of a 'for...in' statement cannot use a type annotation."
The cleanest solution I found is to cast the property as follows
for (let prop in myObject){
console.log(`${prop}: ${myObject[prop as myObjectProps]}`)
}
But I don't know if its the proper way to do it, or if its something safe to do in the first place, or there is in fact a situation that can happen in which a prop will come from myObject but will not be accessible through object[prop]?
as constdon't really make the object immutable or sealed, which is what you'd need for your code to be safe. This is the subject of ms/TS#31652. Does that fully address your question? If so I'll write an answer or close as a duplicate. If not, what am I missing?as constmakes something immutable by itself. But even if you freeze or seal an object, that won't be tracked in the type.