0
votes

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:

  1. These are the only keys and values allowed for the object it represents (e.g. the defaultConfig object should not contain foo: "bar" as a key/value pair, however this would be allowed if DefaultConfig included the index signature.
  2. When I need to call defaultConfig.state.initialState, I don't want it to return the type as any, which is what happens if that index signature is included. Same with if I create a const const foo = assignDefaults(userConfigObject) then call foo.state.initialState.
  3. 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?

1

1 Answers

1
votes

I figured it out...

- Object.keys(userConfig).forEach(key => {
+ (Object.keys(userConfig) as (keyof DefaultConfig)[]).forEach(key => {