JCAguilera
JCAguilera

Reputation: 976

Typescript dynamic union return type based on arguments

I have a function that looks like this:

function getFirstDefined(...values: any) {
    for (let value of values) {
        if (!!value) {
            return value;
        }
    }
    return undefined;
}

It basically gets the first non undefined value in the argument list and returns it.

The problem is I have to add types to it, and I would like for it to have a dynamic union type based on the types of the arguments passed to the method.

For example:

const var1: string = "Hello world!";
const var2: number;
const var3: boolean = false;

// Should have return type: string | number | boolean | undefined
getFirstDefined(var1, var2, var3);

I tried to do this:

function getFirstDefined<T extends any>(...values: T[]): T | undefined {
    for (let value of values) {
        if (!!value) {
            return value;
        }
    }
    return undefined;
}

But it "locks" the function and only allows values of the same type.

const var1: string = "Hello world!";
const var2: number;
const var3: boolean = false;

// Error: var2 and var3 are not strings
getFirstDefined(var1, var2);

Is it possible to do something like this?

Upvotes: 0

Views: 78

Answers (1)

sno2
sno2

Reputation: 4173

You can instead change your generic to be an array itself. Then, it will allow the items to be different. After that, index the array generic by number to get a union of the types of the values in the array and put that with a union to undefined which should get your expected behavior.

function getFirstDefined<T extends unknown[]>(...values: T): T[number] | undefined {
    for (let value of values) {
        if (!!value) {
            return value;
        }
    }
    return undefined;
}

const foo = getFirstDefined("hey", 2, 5); // string | number | undefined
const asdf = getFirstDefined(5, "hey"); // string | number | undefined
const asdf2 = getFirstDefined(); // undefined

TypeScript Playground Link

Upvotes: 3

Related Questions