typescriptgeneric-type-parameters

How to implement generic type of nested object


What I have?

The object of settings like this:

const settings = {
  groupOne: {
    settingOne: 'string',
    settingTo: 123,
  },
  groupTwo: {
    settingThree: true,
  },
};

What I want?

Function (and its type) that gets a group key and a setting key as parameters and returns value of it.

type GetSettingFunction = (group: GroupKey, setting: SettingKey<typeof group>)
  => Setting<typeof group, typeof setting>;

const getSetting: GetSettingFunction = (group, setting) => {
  // some code here
}

I have implemented some intermediate types and it seems working

type Settings = typeof settings;

type GroupKey = keyof Settings;

type Group<T extends GroupKey> = {
  [K in GroupKey]: T extends K ? Settings[T] : never;
}[T];

type SettingsGroup<T extends GroupKey> = {
  [K in GroupKey]: K extends T ? Group<T> : never;
}[T];

type SettingKey<T extends GroupKey> = {
  [K in GroupKey]: K extends T ? keyof SettingsGroup<K>: never;
}[T];

But I can not understand how to implement final type Setting<GroupKey, SettingKey>. Can you help me, please?


Solution

  • I'm going to vastly simplify this for you. You have a lot of complicated utility types that aren't doing you any favors.

    type GetSettingFunction = <
      GroupKey extends keyof Settings,
      SettingKey extends keyof Settings[GroupKey]
    >(
      group: GroupKey,
      setting: SettingKey
    ) => Settings[GroupKey][SettingKey];
    

    A few things here.

    1. The function needs to be generic. Otherwise typeof group is just GroupKey. So the function needs to know the type of the specific key, which means it needs to be generic.
    2. And the function needs to be generic over the setting key as well, since that's needed to derive the return type. And this will use the first generic type to figure out its constraint.
    3. You can just drill into a type like Settings[GroupKey][SettingKey] without a mapped or conditional type.

    Now this works:

    const getSetting: GetSettingFunction = (group, setting) => {
      return settings[group][setting]
    }
    
    getSetting('groupOne', 'settingOne') // string
    

    Or to put it another way, you can write your Setting type like this:

    type Setting<
      GroupKey extends keyof Settings,
      SettingKey extends keyof Settings[GroupKey]
    > = Settings[GroupKey][SettingKey]
    

    See Playground