问题
I have a JSON object imported from a JSON file (with resolveJsonModule: true
).
The object looks like this:
"myobject": {
"prop1": "foo",
"prop2": "bar"
}
and it's type therefore looks like this:
myobject: { prop1: string, prop2: string }
That's very nice but when I try to use a for...in
loop,
for (const key in myobject) {
console.log(myobject[key])
}
I get this error:
TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ "prop1": string; "prop2": string; }'.
No index signature with a parameter of type 'string' was found on type '{ "prop1": string; "prop2": string; }'.
I understand that this means the iterator key
is of type string
and not of type 'prop1' | 'prop2'
. But I don't understand why the iterator doesn't get this type because I'm explicitly iterating through the property names of myobject
. Did I miss a tsconfig property that enables this behavior?
I would like not to do this:
for (const key in myobject) {
console.log(myobject[key as 'prop1' | 'prop2'])
}
Because:
- I might add new properties in the future; and
- this seems a bit cheaty, and I feel like there is a better way to do that.
回答1:
A better way to this is:
for (const key in myobject) {
console.log(myobject[key as keyof typeof myobject])
}
In this way, it won't break when you add a property or rename it
回答2:
if you want to have an object to be dynamic in the future create a model like this
interface PropertyItemModel {
propName: string;
propValue: string;
}
and in the component you can fetch data by loop
export class AppComponent {
items: PropertyItemModel[] = [];
constructor() {
this.items = [
{ propName: "1", propValue: "foo" },
{ propName: "2", propValue: "bar" }]
this.items.forEach(item => {
console.log(`name: ${item.propName} - value: ${item.propValue}`)
});
}
}
回答3:
Three solutions for typing for...in
loops, I am aware of:
1. Type assertion
A type assertion will force key
type to be narrowed to myobject
keys:
for (const key in myobject) {
console.log(myobject[key as keyof typeof myobject])
}
Playground
2. Declare key variable explicitely
The key variable cannot be typed inside the for-in loop, instead we can declare it outside:
let key: keyof typeof myobject // add this declaration
for (key in myobject) {
console.log(myobject[key]) // works
}
Playground
3. Generics
function foo<T>(t: T) {
for (const k in t) {
console.log(t[k]) // works
}
}
foo(myobject)
Playground
Why is this necessary?
key
in a for...in
loop will by design default to type string. This is due to the structural type system of TypeScript: the exact properties' keys shape is only known at run-time, the compiler cannot statically analyze, what properties are present on the object at compile-time. A key
type narrowed to myobject
properties would make the for...in
loop an unsafe operation type-wise.
More infos
Note: Some linked resources discuss Object.keys
, for which the same argumentation holds.
Why doesn't Object.keys return a keyof type in TypeScript? - by Ryan Cavanaugh
Comment by Anders Hejlsberg in TypeScript#12253 - also mentions
for...in
TypeScript#32321 links to a multitude of duplicate issues
Specific comment towards for...in by Anders Hejlsberg:
I have my doubts about this one. In
for (var k in x)
wherex
is of some typeT
, it is only safe to say thatk
is of typekeyof T
when the exact type ofx
isT
. If the actual type ofx
is a subtype ofT
, as is permitted by our assignment compatibility rules, you will see values ink
that are not of typekeyof T
.
来源:https://stackoverflow.com/questions/59233965/typescript-element-implicitly-has-type-any-with-for-in-loops