TypeScript Error with object key which is number

I have the following object.

type Test = {
  0: number[];
  1: number[];
};

There will always be 2 keys named '0' and '1' in the object.

I want to iterate each key in the object and print the number in the value.

const logEveryItem = (test: Test): void => {
  for (let key in test) {
    test[key].forEach((i: number) => console.log(i));
  }
};

But test[key] from the 3rd line, TypeScript gives the following error.

TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'Test'. No index signature with a parameter of type 'string' was found on type 'Test'.

How should I fix this issue?


Solution 1:

Why there is an error ?

Because for .. in operator, by the default, uses string type for iterable property.

const logEveryItem = (test: Test): void => {
    // let prop: string
    for (let prop in test) {

    }
};

string type is to wide for test keys. For instance, you are not allowed to use test['any string'] to access a property. IN this case prop should be 0 | 1. TypeScript does not infer it as 0 | 1 be the default because how in operator works. See this example:

type Test = {
    0: number[];
    1: number[];
};

class SuperTest {
    foo = 'hello'
}

class SubTest extends SuperTest {
    0: number[] = [1, 2, 3]
    1: number[] = [4, 5, 6]
}

const logEveryItem = (test: Test): void => {
    // let prop: string
    for (let prop in test) {
        console.log(prop)
        test[prop].forEach() // run time error
    }
};

logEveryItem(new SubTest())

Somebody could expect that it will log "0" and "1", but it will also log "foo". Please see for .. in docs.

... including inherited enumerable properties.

So, now we know that it is not safe to assume that we will only iterate through 0 | 1. This is why prop infers as a string.

If you are interested in object values, I think it worth using Object.values:


const logEveryItem2 = (test: Test): void => {
    Object.values(test)
        .forEach(elem => {
            console.log(elem)
        })
};

Solution 2:

You either want a record:

type Test = Record<string, number[]> // or Record<number, number[]> if you want your key to be a number. 

which is the same as :

type Test = {
  [key: string]: number[]
};

or a tuple

type Test = [number[], number[]] // could be of the length you want.