Better way to check for Array<any> type

123 Views Asked by At

My root problem is following, I need to detect that a type is Array<any> (not any other Array type).

I can detect this with 2 combined conditions : T extends Array<any> and Array<any> extends

How could I write a conditional type without having to resort to a double ternary like following:

type foo<T> = T extends Array<any> ? Array<any> extends T ? T : 'not any array' : 'not any array';
2

There are 2 best solutions below

0
mochaccino On BEST ANSWER

Here is a method that works using only 1 conditional type, borrowing the trick from this answer:

type foo<T> = [1[] & T, any[]] extends [0[], T] ? T : "not any array";

There's two checks going on here: one two check for any[], and the other two disallow never[]. To check for any[], we're using the same principle as the linked answer: any allows us to do some crazy things, like assigning 1[] to 0[]. This however, also allows never[] to slip by.

To address never[], we use another check. Because never is the bottom type, nothing is assignable to it,. This means that checking if any[] is assignable to T is all we need.

Example tests:

type T01 = foo<any[]>
//   ^? any[]
type T02 = foo<never[]>
//   ^? "not any array"
type T03 = foo<number[]>
//   ^? "not any array"
type T04 = foo<{ bar: any }>
//   ^? "not any array"
type T05 = foo<{ 1: any }>;
//   ^? "not any array"

Playground

3
Dimava On

Here's how it is possible to do what I assume you want to do (please correct me in comments if needed)

type IfAny<T, Y, N> = 0 extends (1 & T) ? Y : N;
type IfAnyArray<T, Y, N> = T extends ReadonlyArray<infer V> ? IfAny<V, Y, N> : N;
type IsAnyArray<T> = IfAnyArray<T, 'is any[]', 'is not any[]'>


type test_fooz = IsAnyArray<(number | string)[]>
//   ^?
type test_foo0 = IsAnyArray<number[] | string[]>
//   ^?
type test_foo1 = IsAnyArray<string[]>
//   ^?
type test_foo2 = IsAnyArray<any[]>
//   ^?
type test_foo3 = IsAnyArray<readonly string[]>
//   ^?
type test_foo4 = IsAnyArray<readonly any[]>
//   ^?
type test_foo5 = IsAnyArray<MyArray<string>>
//   ^?
type test_foo6 = IsAnyArray<MyArray<any>>
//   ^?
type test_excb = IsAnyArray<(string & {brand: 'myBrand'})[]>
//   ^?
type test_excB = IsAnyArray<(string & {brand?: 'myBrand'})[]>
//   ^?


class MyArray<T> extends Array<T> {
    x!: number;
}

And this is if you want Exclude:

type ExcludeArrayOf<T, E> = T extends ReadonlyArray<infer V extends E> ? IfAny<V, T, never> : T;
 
type test_excz = ExcludeArrayOf<(number | string)[], string>
//   ^?
type test_exc0 = ExcludeArrayOf<number[] | string[], string>
//   ^?
type test_exc1 = ExcludeArrayOf<string[], string>
//   ^?
type test_exc2 = ExcludeArrayOf<any[], string>
//   ^?
type test_exc3 = ExcludeArrayOf<readonly string[], string>
//   ^?
type test_exc4 = ExcludeArrayOf<readonly any[], string>
//   ^?
type test_exc5 = ExcludeArrayOf<MyArray<string>, string>
//   ^?
type test_exc6 = ExcludeArrayOf<MyArray<any>, string>
//   ^?
type test_excb = ExcludeArrayOf<(string & {brand: 'myBrand'})[], string>
//   ^?
type test_excB = ExcludeArrayOf<(string & {brand?: 'myBrand'})[], string>
//   ^?