I have the following type definitions:
interface DefaultConfig {
state: {
initialState: AllState;
};
}
type UserConfig = {
[K in keyof DefaultConfig]: DefaultConfig[K];
};
// Could also use 'type UserConfig = Partial<DefaultConfig>;'
interface AssignDefaults {
(userConfig: UserConfig): DefaultConfig;
}
I use these in the following function:
const defaultConfig: DefaultConfig = {
state: {
initialState: defaultInitialState
}
};
const assignDefaults: AssignDefaults = userConfig => {
const thisUserConfig = userConfig;
Object.keys(userConfig).forEach(key => {
if (key in defaultConfig) {
const maybeObject = userConfig[key];
if (!!maybeObject && maybeObject.constructor === Object) {
thisUserConfig[key] = {
...(defaultConfig[key] || {}),
...userConfig[key]
};
}
}
});
return { ...defaultConfig, ...thisUserConfig };
};
What I expect to happen is that userConfig[key]
should be equal to keyof DefaultConfig
, as that's what is defined in the UserConfig
index signature.
However, this doesn't work as Object.keys(...)
defines its return value as string[]
. This causes a Typescript error:
Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'UserConfig'.
No index signature with a parameter of type 'string' was found on type 'UserConfig'.
It seems Typescript is unable to determine that keyof DefaultConfig
is also a string.
I know I can remove this error by including [key: string]: any
to the DefaultConfig
interface, however I would much prefer not to do this as:
- These are the only keys and values allowed for the object it represents (e.g. the
defaultConfig
object should not containfoo: "bar"
as a key/value pair, however this would be allowed ifDefaultConfig
included the index signature. - When I need to call
defaultConfig.state.initialState
, I don't want it to return the type asany
, which is what happens if that index signature is included. Same with if I create a constconst foo = assignDefaults(userConfigObject)
then callfoo.state.initialState
. - I don't like using
any
in my type definitions unless absolutely necessary.
Is this a limitation of Typescript or is there some workaround for this?