Type guards for types of arrays

10,607

Solution 1

The answer is User-Defined Type Guards.

You can define a type guard of your own that will determine whether the array is array of numbers or not:

function isNumbers(array: number[] | string[]): array is number[] {
    return typeof array[0] === 'number'
}

Read more about user defined type guards here.

The following is a working example of your code:

function join(array: number[] | string[]): string {
    if (isNumbers(array)) {
        return foo(array);
    } else {
        return bar(array)
    }
}

function foo(n: number[]): string {
    return n.join();
}

function bar(s: string[]): string {
    return s.join();
}

function isNumbers(array: number[] | string[]): array is number[] {
    return typeof array[0] === 'number'
}

TypeScript playground example.

Solution 2

JeB's answer was a very good start for me but I needed something a little more robust because I have to deal with arbitrary input. Here's what I came up with:

function isNumberArray(value : unknown) : value is number[] {
    if (!Array.isArray(value)) {
        return false;
    }

    if (value.some((v) => typeof v !== "number")) {
        return false;
    }

    return true;
}
Share:
10,607
JeB
Author by

JeB

Updated on July 28, 2022

Comments

  • JeB
    JeB almost 2 years

    Using typeof type guards for non-array types is pretty straightforward (example from the docs):

    function padLeft(value: string, padding: string | number) {
        if (typeof padding === "number") {
            return Array(padding + 1).join(" ") + value;
        }
        if (typeof padding === "string") {
            return padding + value;
        }
        throw new Error(`Expected string or number, got '${padding}'.`);
    }
    

    However, when it comes to array types it gets more complicated. I would assume that the following code would work:

    function join(array: number[] | string[]): string {
        if (typeof array[0] === 'number') {
            return foo(array);
        } else {
            return bar(array)
        }
    }
    
    function foo(n: number[]): string {
        return n.join();
    }
    
    function bar(s: string[]): string {
        return s.join();
    }
    

    Seems quite simple to me: the expected type is either array of numbers or array of strings.
    If the type of the first element in the array is number then the array is of type number[]. Otherwise, this is a strings array.

    Unfortunately, TypeScript compiler is not that smart and I get a compilation error:

    Argument of type 'number[] | string[]' is not assignable to parameter of type 'string[]'

    How do I make it work?