0

basically speaking i have two interfaces:


interface config {
    lang: "en" | "de";
    test: number;
}

interface Accessor {
    key ( k: keyof config ): config[k];
}

and i want to dynamically have the type of whatever i access from the config type, via the Accessor object. is that possible?



note: my code is a lot more complicated and i cannot just access the keys directly, but it boils down to this in the end

5
  • 1
    That k is a parameter name, not a type. Instead you want key to be generic in the type of k. Like this. Does that meet your needs and I can write up an answer? Or am I missing something? Commented May 30, 2022 at 18:20
  • thank you yes that solves my problem. can you quickly explain to me how / why it works like that? Commented May 30, 2022 at 18:24
  • 1
    I can write up an answer explaining it, but "quickly" isn't something I can commit to Commented May 30, 2022 at 18:25
  • oh okay, well i don't need it quickly, i just don't want to bother you that much. is there at least a place in the documentation where i can find more information? thank you anyways for your help! Commented May 30, 2022 at 18:29
  • 1
    i may have misread your first comment and read "Does that meet your needs or do I need to write up an answer", instead of "Does that meet your needs and I can write up an answer". sorry! Commented May 30, 2022 at 18:40

1 Answer 1

2

The key() method signature in your Accessor interface should return an indexed access type of the form Config[X] where X is the type of the k parameter. Your attempt, Config[k], is not valid, because k is the value of the parameter; you need its type. This can be remedied using the typeof type operator like so:

interface Accessor {
    key(k: keyof Config): Config[typeof k];
}

This now compiles with no error. And you can even use it:

declare const a: Accessor;
const langProp = a.key("lang");
// const langProp: number | "en" | "de" 🤔
const testProp = a.key("test");
// const testProp: number | "en" | "de" 🤔

But as you can see, the behavior is not really what you want. The type of result is the union of possible property types of Config. In fact, the key() call signature returns the same type no matter what the input is. The type of k is just keyof Config. And keyof Config is the union "lang" | "test". And Config[typeof k] is Config[keyof Config] is Config["lang" | "test"] is Config["lang"] | Config["test"] is "en" | "de" | number. There's nothing about the call signature which pays attention to which key is being passed in.


What you want is for a.key("lang") to return "en" | "de" and for a.key("test") to return number. That implies you want key() to be generic in the type of k. That is, instead of k being of type keyof Config, it should be of a generic type, let's call it K, which is constrained to keyof Config:

interface Accessor {
    key<K extends keyof Config>(k: K): Config[typeof k];
}

Here K is a generic type parameter, declared inside angle brackets after the name of the method but before the opening parenthesis for the function parameter list. Once it is so declared, we can refer to K as a type elsewhere in the call signature.

And once we do that, everything works as desired:

declare const a: Accessor;
const langProp = a.key("lang");
// const langProp: "en" | "de" 👍
const testProp = a.key("test");
// const testProp: number 👍

Note that the typeof type operator isn't really necessary if you already have a name for the type. In our case, k is of type K, so typeof k can just be written as K:

interface Accessor {
    key<K extends keyof Config>(k: K): Config[K];
}

And everything still works.

Playground link to code

Sign up to request clarification or add additional context in comments.

1 Comment

that is so detailed and understandable, thank you so much for this!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.