Search code examples
typescriptgenericstypes

Generic TypeScript function returning type of object key


I have the following code structure to manage some settings :

interface Settings {}

interface ServerSettings extends Settings {
    host: string;
    port: number;
    valid: boolean;
}

interface SomeOtherSettings extends Settings {
    enabled: boolean;
}

class SettingsProvider {
    constructor(private settings: Settings) {}

    public get<S extends Settings>(
        key: keyof S,
    ): S[typeof key] {
        return (this.settings as unknown as S)[key];
    }
}

const settings: ServerSettings & SomeOtherSettings = {
    host: 'localhost',
    port: 8080,
    valid: true,
    enabled: false,
};
const provider = new SettingsProvider(settings);
const port = provider.get<ServerSettings>('port');

The problem is that the type of port is const port: string | number | boolean (the union of all the possible types of ServerSettings), and not number, as one would expect, given the return type of the function being S[typeof key].

How can I get the right type narrowed ?

One solution could be to pass a second generic to the function like Key extends keyof S but this seems very redundant.


Solution

  • One solution could be to pass a second generic to the function like Key extends keyof S but this seems very redundant.

    This sounds like typescript - Providing only some types for generic type - Stack Overflow.

    A workaround is to curry the function:

    class SettingsProvider {
        constructor(private settings: Settings) {}
    
        public get<S extends Settings>() {
            return <K extends keyof S>(key: K) => {
                return (this.settings as S)[key];
            }
        }
    }
    
    const settings: ServerSettings & SomeOtherSettings = {
        host: 'localhost',
        port: 8080,
        valid: true,
        enabled: false,
    };
    const provider = new SettingsProvider(settings);
    
    const port = provider.get<ServerSettings>()('port');
    
    const getter = provider.get<ServerSettings>();
    const host = getter('host');
    const valid = getter('valid');