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

5.5k Views Asked by At

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:

  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.
4

There are 4 best solutions below

0
On BEST ANSWER

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

0
On

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?

That's because you can extend an object property and typescript will still consider it a type of the same object. E. g:

interface ABC {
  a: string;
  b: string;
  c: number;
}
function foo(abc: ABC) {
  for (const k in abc) { // const k: string
  const v = abc[k];
  // ~~~~~~ Element implicitly has an 'any' type
  // because type 'ABC' has no index signature
 }
}
  
const x = {a: 'a', b: 'b', c: 2, d: new Date()};
foo(x); //OK, no errors

That's because ts considers x to be an ABC type because it has the properties a, b and c.

To fix the error in the function foo, you need to say that k is of the type of keyof ABC.

function foo(abc: ABC) {
    let k: keyof ABC;
    for (k in abc) { // let k: "a" | "b" | "c"
    const v = abc[k]; // Type is string | number
    }
 }
1
On

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

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.