RandomQuestionsSO
RandomQuestionsSO

Reputation: 23

Typescript: Can you get a return type of a function based on the value of a single key in a parameter object?

I have this hook that makes an api call and caches an object used throughout my app.

Rather than need to have a bunch of helper functions to get specific values off this object id like to pass in the key of the object and get that key value returned, the hook also has access to an array of these objects and can select a specific one, or it returns the default for the app.

The hook functions, I am not struggling with writing a type definition that fits the input and output in typescript.

Here is what I have so far:

type OddParams = {
    thing: number;
    thing2: number;
    key?: PotatoKey;
}

type Banana = {
    id: string;
    name: string;
}

type Potato = {
    id: string;
    name: string;
    phone: number;
    monkey: Banana;
}

type PotatoKey = keyof Potato;
type Params = OddParams | PotatoKey;

type ObjectType<T> = 
    T extends unknown ? Potato :
    T extends 'id' | 'name' ? string :
    T extends 'phone' ? number :
    T extends 'monkey' ? Banana :
    never;

function getPotato<T extends Params>(desc =  (undefined as unknown) as T): ObjectType<T>{
    console.log(desc);
    return '2' as any
};

getPotato() //returns type Potato (returns the default potato in implemntation )
getPotato('id') //returns type string (would return id of the default potato in implemntation )
getPotato({thing: 2, thing2: 1, key: 'id'}) //returns type string (would return id of potato[2][1] in implemntation )

Still need to add in the object part into the ObjectType variable. Also looking for better ways of declaring based on the key even when its just the string param. Above I do T extends 'id' and so forth for each key, but in the production problem there are 20+ keys so having a bunch of ternary for each return type is extremely unreadable in the end.

Any ideas in how to achieve this?

Upvotes: 2

Views: 306

Answers (1)

JJWesterkamp
JJWesterkamp

Reputation: 7916

Your getPotato function basically needs three different signatures, and three corresponding ways of determining the matching return types. This can be achieved with function overloads.

The first and second (no argument, and string argument, respectively) are pretty straightforward to do:

function getPotato(): Potato;
function getPotato<K extends keyof Potato>(key: K): Potato[K];

For the third overload you will need to 'take' the value of the key property from the params object that you give it. Before that works, the key property must be of type keyof Potato:

type Params = {
    thing: number;
    thing2: number;
    key: keyof Potato;
}

function getPotato<T extends Params>(params: T): Potato[T['key']]

This says: params is of type T, which must be a specific version of Params. And then as return type, you can take the key off T to find the type for it on Potato

Finally you'll need the actual function implementations, which should account for all these three cases:

function getPotato(arg?: keyof Potato | Params): Potato | Potato[keyof Potato] {
    if (arg === undefined) {
        return ... // first overload
    }

    if (typeof arg === 'string') {
        return ... // second overload
    }

    const { thing, thing2, key } = arg
    return ... // third overload
};

Putting everything together this gives us the following code:

type Banana = {
    id: string;
    name: string;
}

type Potato = {
    id: string;
    name: string;
    phone: number;
    monkey: Banana;
}

type Params = {
    thing: number;
    thing2: number;
    key: keyof Potato;
}

function getPotato(): Potato
function getPotato<K extends keyof Potato>(key: K): Potato[K]
function getPotato<T extends Params>(params: T): Potato[T['key']]
function getPotato(arg?: keyof Potato | Params): Potato | Potato[keyof Potato] {
    // ... 
};

I tested the types with the following assignments:

const a: Potato = getPotato()
const b: string = getPotato('id')
const c: number = getPotato({ thing: 2, thing2: 1, key: 'phone' })
const d: Banana = getPotato('monkey')

Upvotes: 1

Related Questions