To handle this type of situation, it is best to use a constrained generic type rather than a concrete type. This means that anything working with such a type will also need to support generics. Typically, you would utilize a helper function to validate whether a value matches the specific type. Here's an example:
type SwapPair<T> = T extends readonly [any, any] ? readonly [T[1], T[0]] : never;
type AsArrayOfPairs<T> = ReadonlyArray<readonly [any, any]> &
{ [K in keyof T]: SwapPair<T[K]> }
const asArrayOfPairs = <T extends AsArrayOfPairs<T>>(pairArray: T) => pairArray;
The SwapPair<T>
type transforms a pair type like [A, B]
into [B, A]
. Thereadonly
keyword adds more flexibility but can be removed if needed. Similarly, the AsArrayOfPairs<T>
type flips all pair-like properties around, ensuring that the new type matches the candidate type T
perfectly if it's a valid array of pairs.
For instance,
AsArrayOfPairs<[[number, number]]>
results in
readonly (readonly [any, any])[] & [readonly [number, number]]
, which is an appropriate match for
[[number, number]]
. Conversely,
AsArrayOfPairs<[[string, number]]>
yields
readonly (readonly [any, any])[] & [readonly [number, string]]
, indicating that
[[string, number]]
does not align with the expected structure.
The utility function asArrayOfPairs
validates a value without widening it. For example:
const goodVal = asArrayOfPairs([[1, 2], ["a", "b"], [true, false]]);
// const goodVal: ([number, number] | [string, string] | [boolean, boolean])[]
In this case, goodVal
is correctly inferred as
Array<[number, number] | [string, string] | [boolean, boolean]>
. On the other hand:
const badVal = asArrayOfPairs([[1, 2], ["a", "b"], [true, false], ["", 1]]);
// error! (string | number)[] is not assignable
An error is raised due to the problematic entry ["", 1]
, where the compiler struggles to narrow it down and eventually concludes that it doesn't conform to a valid pair type.
While there are alternative approaches, many straightforward methods may not produce the desired outcome. For example, mapping tuple inference can sometimes result in unexpected widenings, making validation tricky. Therefore, implementing an array swap method post-inference seems to be a more reliable approach.
I hope these insights prove helpful. Best of luck!
Link to code