Given the following code for example purposes only:
type tuple = [key: string, value: number];
function foo(arg: tuple | Array<tuple>) {
const arr = Array.isArray(arg[0]) ? arg : [arg];
// The type of arr is: tuple | (tuple | tuple[])[]
console.log(arr.length);
}
foo(['key', 111]);
foo([['key', 222]]);
I've also tried typeof arg[0] === 'string' ? [arg] : arg
but that also results in the same result. Running the code has the expected outcome, that arr
is always an array of tuples, but TypeScript doesn't "know" it.
How can I get typescript to correctly narrow the type of arr
to be tuple[]
without asserting it explicitly?
CodePudding user response:
The compiler only sees that you're assigning either arg
or [arg]
to arr
. It doesn't change what it thinks the type of arg
is based on Array.isArray(arg[0])
, so arr
's type is the union of the types of arg
and [arg]
. The type of arg
is tuple | tuple[]
so the type of [arg]
is (tuple | tuple[])[]
; the union of those types simplifies to tuple | (tuple | tuple[])[]
since tuple[]
is a subset of (tuple | tuple[])[]
.
You can wrap Array.isArray
in a user-defined type guard to have the compiler interpret things the way you want.
function isArrayOfTuples(arg: tuple | tuple[]): arg is tuple[] {
return Array.isArray(arg[0]);
}
function foo(arg: tuple | tuple[]) {
const arr: tuple[] = isArrayOfTuples(arg) ? arg : [arg];
console.log(arr.length);
}
Otherwise I think you will have to assert the types explicitly to do this.
const arr: tuple[] = Array.isArray(arg[0]) ? arg as tuple[] : [arg] as tuple[];
Another option which may be simpler to implement in general is to change the type of arg
to just tuple[]
. Callers who want to pass a single tuple
can wrap the argument in an array.