3

I have a function that receives an array, but those can be of different type. Depending on the type I need to format differently:

public format(value: Foo[] | Bar[]) {
  // this does not work
  if (value instanceof Foo[]) ...
}

I know I can use instanceof to check if I have an object of a certain class in Typescript.

new Foo() instanceof Foo // true

This also works for checking if I have an Array.

new Array<Foo> instanceof Array // true

But I cannot check if my Array actually is typed to Foo

new Array<Foo>() instanceof Array<Foo> 
// The right-hand side of an 'instanceof' expression must be of type 'any' 
// or of a type assignable to the 'Function' interface type.

Is there any way to explicitly check for the type of values of an Array?

2
  • 1
    You'll probably have to check for the type of the first item in the array (if any) Commented Mar 3, 2020 at 12:20
  • Just use instanceof for 1 element of the array, since it's guaranteed that all the elements of the Array are of the same time, according to your definitions. Commented Mar 3, 2020 at 19:31

2 Answers 2

9

At runtime there's no difference between Array<Foo> and Array<Bar>; the static type system is erased from the emitted JavaScript, so all you have is some JavaScript array. So you'll need to write your own test that operates at runtime and then tell the compiler what you're doing so you get the benefits of static typing.


One way would be to write some suitable user-defined type guard functions, which would let you do this:

public format(value: Foo[] | Bar[]) {
    const isFooArray = isArrayOf(isInstanceOf(Foo));

    if (isFooArray(value)) {
        // true block
        for (const foo of value) {
            const f: Foo = foo; // okay
        }
    } else {
        // false block
        for (const bar of value) {
            const b: Bar = bar; // okay
        }
    }
}

The compiler understands that inside the true block, value has been narrowed from Foo[] | Bar[] to just Foo[], and that inside the false block, value has been narrowed from Foo[] | Bar[] to just Bar[]. This has to do with the type signature for isFooArray(), a type guard created by composing the output of two other functions, isArrayOf() and isInstanceOf().

Let's examine their definitions:

const isArrayOf = <T>(elemGuard: (x: any) => x is T) =>
    (arr: any[]): arr is Array<T> => arr.every(elemGuard);

The function isArrayOf() takes a type guard function elemGuard for a single array element and returns a new type guard that calls elemGuard on every element of an array. If all the elements pass the test, then you have an array of the guarded type. If even a single element does not pass, then you don't. You could, if you want, check just one element, but you run the risk of accidentally treating a heterogeneous array like Array<Foo | Bar> as a Foo[]. Also note that this implies an empty array [] will always pass the test; so an empty array will be considered both a Foo[] and a Bar[].

const isInstanceOf = <T>(ctor: new (...args: any) => T) =>
    (x: any): x is T => x instanceof ctor;

The isInstanceOf() function just wraps the normal instanceof test into a user-defined type guard suitable to use with isArrayOf().

So const isFooArray = isArrayOf(isInstanceOf(Foo)) is a composite type guard that specifically checks to see if the array it is examining is a Foo[], by examining each element and doing an instanceof check.


Playground link to code

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

1 Comment

Great!, I try this solution and it works for me. But I still adding "as SecondType" in the params of function used in the false block.
0

You would need to retrieve an item and check that type. But because TypeScript has static typing, the objects of an Array are always from the type Foo.

2 Comments

Why would you wanna check the type of the elements though? TypeScript ensures that every element that the array contains is from the type Foo. A context would be helpful.
I guess you would have to check an item then.

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.