Lukas
Lukas

Reputation: 10380

If parameter is array return array of values otherwise return value

I want to allow a string or an array of strings as a parameter. Depending on whether a string or an array of strings have been passed I want to return either a value or an array of values.

function(input: string | string[]): inputIsArray ? returnValue[] : returnValue {}

How can I write proper types for this?

Upvotes: 7

Views: 2206

Answers (1)

T.J. Crowder
T.J. Crowder

Reputation: 1075755

I think I'd use overloads (but conditional types can be used, see below). In this example, I've used number for returnType but of course that's just for the example:

function example(input: string): number;
function example(input: string[]): number[];
function example(input: string | string[]): number | number[] {
    if (Array.isArray(input)) {
        return (<string[]>input).map(str => Number(str));
    }
    return Number(<string>input);
}

const result1 = example("42");         // `result1`'s type is `number`
const result2 = example(["42", "67"]); // `result2`'s type is `number[]`

(on the playground)

Note that the third one of those is just the implementation signature. The only valid call signatures for example there are the first two.


Re your comment asking if this is possible with conditional types, off-site I pinged Titian Cernicova-Dragomir, one of SO's experts on TypeScript. He said he'd seen the question and was about to post the overloads answer when he saw I had :-), and that he'd also use overloads for this — but it is possible with conditional types if you want to do it that way, and doing so opens up a third call signature. Here's what he came up with:

function example<T extends string | string[]>(input: T): T extends string ? number : number[];
function example(input: string | string[]): number | number[] {
    if (Array.isArray(input)) {
        return (<string[]>input).map(str => Number(str));
    }
    return Number(<string>input);
}

const result1 = example("42");         // `result1`'s type is `number`
const result2 = example(["42", "67"]); // `result2`'s type is `number[]`

(on the playground)

Notice how introducing T lets us use it both in the type of the parameter and the (conditional) type of the return value. (That was the key part I missed trying to apply conditional types to this.)

He pointed out that the conditional types version opens up a third way to call it: With an argument whose type is string | string[], not one or the other:

declare const value: string | string[];
let result3 = example(value) // `result3`'s type is `number | number[]`. Would not have worked with overloads.

(on the playground)

So if you want that third call signature, you can use conditional types to get it. Otherwise, overloads provide just the first two signatures.

Upvotes: 6

Related Questions