简体   繁体   中英

Typescript element implicitly has type any with for...in loops

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' '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:

  1. I might add new properties in the future; and
  2. this seems a bit cheaty, and I feel like there is a better way to do that.

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

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.

I have my doubts about this one. In for (var k in x) where x is of some type T , it is only safe to say that k is of type keyof T when the exact type of x is T . If the actual type of x is a subtype of T , as is permitted by our assignment compatibility rules, you will see values in k that are not of type keyof T .

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}`)
     });
  }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM