This is related to questions like Why doesn't Object.keys()
return a keyof type in TypeScript?. The answer to both is that object types in TypeScript are not exact; values of object types are allowed to extra properties not known about by the compiler. This allows interface and class inheritance, which is very useful. But it can lead to confusion.
For example, if I have a value nameHaver
of type {name: string}
, I know it has a name
property, but I don't know that it only has a name
property. So I can't say that Object.entries(nameHaver)
will be Array<["name", string]>
:
interface NameHaver { name: string }
declare const nameHaver: NameHaver;
const entries: Array<["name", string]> = Object.entries(nameHaver); // error here: why?
entries.map(([k, v]) => v.toUpperCase());
What if nameHaver
has more than just a name
property, as in:
interface NameHaver { name: string }
class Person implements NameHaver { constructor(public name: string, public age: number) { } }
const nameHaver: NameHaver = new Person("Alice", 35);
const entries: Array<["name", string]> = Object.entries(nameHaver); // error here: ohhh
entries.map(([k, v]) => v.toUpperCase()); // explodes at runtime!
Oops. We assumed that nameHaver
's values were always string
, but one is a number
, which will not be happy with toUpperCase()
. The only safe thing to assume that Object.entries()
produces is Array<[string, unknown]>
(although the standard library uses Array<[string, any]>
instead).
So what can we do? Well, if you happen to know and are absolutely sure that a value has only the keys known about by the compiler, then you can write your own typing for Object.entries()
and use that instead... and you need to be very careful with it. Here's one possible typing:
type Entries<T> = { [K in keyof T]: [K, T[K]] }[keyof T];
function ObjectEntries<T extends object>(t: T): Entries<T>[] {
return Object.entries(t) as any;
}
The as any
is a type assertion that suppresses the normal complaint about Object.entries()
. The type Entries<T>
is a mapped type that we immediately look up to produce a union of the known entries:
const entries = ObjectEntries(nameHaver);
// const entries: ["name", string][]
That is the same type I manually wrote before for entries
. If you use ObjectEntries
instead of Object.entries
in your code, it should "fix" your issue. But do keep in mind you are relying on the fact that the object whose entries you are iterating has no unknown extra properties. If it ever becomes the case that someone adds an extra property of a non-number[]
type to unpassable_tiles
, you might have a problem at runtime.
Okay, hope that helps; good luck!
Playground link to code