1

I'm having issues accessing a property in a object using a string index, where the interface is defined using in keyof.

Take this code:

interface IFilm {
    name: string;
    author: string;
}
type IExtra<T extends  {}> = {
    [index in keyof T]: number;
};

const film : IFilm = { name: "FilmName", author: "AuthorName"};
const extra : IExtra<IFilm> = {  name: 1, author: 2};

Object.keys(film).forEach(X => {
    extra[x] = 3; // Error here!!
});

Where I try to access extra[x] I got this error:

Element implicitly has an 'any' type because expression of type 'any' can't be used to index type 'IExtra'.

I'm getting why: typescript cannot know that x contains only string that are actually valid for the constraint in keyof T. Thus, how can I rewrite this, or make TypeScript aware that this is valid code? I know I can using (extra as any)[x], but I'd like to be "type-checked" :)

Jolly
  • 1,678
  • 2
  • 18
  • 37
  • 3
    Does this answer your question? [Why doesn't Object.keys return a keyof type in TypeScript?](https://stackoverflow.com/questions/55012174/why-doesnt-object-keys-return-a-keyof-type-in-typescript) Eventhough the linked question is *"Why"*, some of the answers provide workarounds for the *"How"* – derpirscher Apr 30 '23 at 14:31
  • Your `x` and `X` have different case so they are different variables – Dimava Apr 30 '23 at 16:31

2 Answers2

1

You issue is that Object.keys<T> returns a string, not a key of T.

You need yo do a type assertion extra[x as keyof IFilm] = 3;

If you want to iterate over typed keys, use a Map.

Matthieu Riegler
  • 31,918
  • 20
  • 95
  • 134
1

The Object.keys doesn't return keyof T, because TS does not guarantes that there are no other keys:

const notAFilm = { name: '123', author: 'asd', blah: true }
const badFilm: IFilm = notAFilm
const badKeys = Object.keys(badFilm)
console.log(badKeys) // ["name", "author", "blah"] 

If you are sure there is no other keys, make an alternative which knows that:

interface IFilm {
    name: string;
    author: string;
}
type IExtra<T extends {}> = {
    [index in keyof T]: number;
};

const film: IFilm = { name: "FilmName", author: "AuthorName" };
const extra: IExtra<IFilm> = { name: 1, author: 2 };

/** works as expected only if there is no unknown keys */
function recordKeys<T extends {}>(o: T): (keyof T)[] {
    return Object.keys(o) as (keyof T)[]
}

recordKeys(film).forEach(x => {
    extra[x] = 3; // works fine
});
Dimava
  • 7,654
  • 1
  • 9
  • 24