7

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?

1 Answer 1

6

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.

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

5 Comments

Don't you see a clever way of writing types, I was assuming something like this: function myFunc<S = string[] | string>(input: S): S extends string[] ? number[] : number;
Btw awesome to get a comment from you again T.J. I believe I already got comments for you >10 years ago :)
@Lukas - I'm afraid I don't know of a way to define the return type in terms of whether the parameter type is an array (other than using overloads :-) ).
@Lukas - I pinged one of the TypeScript experts I know about this. See the updated answer. :-)
Thanks for your effort, that is great to know! It actually works in my code too!

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.