I'm creating a generic class like this:
type Config = {
value: string
}
class MyClass<T> {
configs:{[key in keyof T]: Config },
data: {[key in keyof T]?: string}
constructor(configs: { [key in keyof T]: Config }) {
this.configs = configs;
this.data= {};
}
init() {
Object.entries(this.configs).forEach(([configName, config]) => {
if (!(configName in this.data)) {
this.data[configName] = dosomething(config); // dosomething returns string
}
});
}
}
Then use the class like:
const cls = MyClass({
cfg1: {value: 'value1'},
cfg2: {value: 'value2'},
})
cls.init();
And the cls.data
is expected to be:
{
cfg1: 'calculated1',
cfg2: 'calculated2',
}
But in the forEach
, configName
is string
and config
is unknown
.
I tried ([configName, config]: [key in keyof T, Config]) =>
but that's not working.
How can I do this?
CodePudding user response:
Well the function Object.entries
is not able to correctly infer the types here. When we look at the type definition of this function it looks like this:
entries<T>(o: { [s: string]: T } | ArrayLike<T>): [string, T][];
But you are passing an object of type { [key in keyof T]: Config }
to the function. TypeScript does not see those as comparable and so the type [string, unknown][]
is inferred.
So how can you solve this?
Option 1
The easiest way would be to change { [key in keyof T]: Config }
to { [key : string]: Config }
everywhere.
configs:{[key : string]: Config }
data: {[key : string]: string}
constructor(configs: { [key : string]: Config }) {
this.configs = configs;
this.data= {};
}
This is essentially identically to the code you had before. You didn't restrain the type T
to have any particular shape so the keys of T
could have been any string value anyway. In fact, since we don't use T
anymore the class does not have to be generic.
Let me know if this works for your specific use case. Or if using key in keyof T
makes a difference for you.
Option 2
The second option would be to set a constraint on T
:
class MyClass2<T extends Record<string, any>> { /* ... */ }
Now TypeScript knows that keyof T
produces string
values and the correct types are inferred.
But now we get another error:
this.data[configName] = dosomething(config)
// Type 'string' cannot be used to index type '{ [key in keyof T]?: string | undefined; }'
This is because Object.entries
will always return string
as the first type in the tuple because it is hard-coded in the type definition.
To fix this we have (sadly) to give TypeScript more information:
this.data[configName as keyof T] = dosomething(config);