I'm close to nailing it, but can't find a way around the final TS2322: Type TcolTuple[i] is not assignable to type string | number | symbol
compiler error.
So, here is a utility function rowsToObjects()
that quite a few people probably defined in their projects once or twice, it's somewhat similar to zip() in concept:
const objects = rowsToObjects(
['id', 'color' , 'shape' , 'size' , 'to' ] as const,
[ 1n, 'red' , 'circle' , 'big' , '0x0'] as const,
[ 2n, 'green' , 'square' , 'small' , '0x0'] as const,
[ 3n, 'blue' , 'triangle', 'small' , '0x0'] as const,
)
That outputs:
[
{id: 1n, color: 'red', shape: 'circle', size: 'big', to: '0x0'},
{id: 2n, color: 'green', shape: 'square', size: 'small', to: '0x0'},
{id: 3n, color: 'blue', shape: 'triangle', size: 'small', to: '0x0'},
]
The actual implementation is obviously trivial, but typing it gives me some hard time:
export function rowsToObjects<
Tobj extends { [i in keyof TcolTuple as TcolTuple[i]]: TvalTuple[i] },
TcolTuple extends readonly string[],
TvalTuple extends { [j in keyof TcolTuple]: unknown }
>(cols: TcolTuple, ...rows: TvalTuple[]): Tobj[];
Current code seems logical to me, but the compiler complains about the as TcolTuple[i]
part:
TS2322: Type TcolTuple[i] is not assignable to type string | number | symbol
Type TcolTuple[keyof TcolTuple] is not assignable to type string | number | symbol
Type
TcolTuple[string] | TcolTuple[number] | TcolTuple[symbol]
is not assignable to type string | number | symbol
Type TcolTuple[string] is not assignable to type string | number | symbol
Am I missing something obvious here? The typing is close to satisfactory, but without that as TcolTuple[i]
it does not recognize which value belongs to which key and just unions them all.
I think the main problem you're having with
is that using key remapping prevents the mapped type from being homomorphic (see What does "homomorphic mapped type" mean?), so instead of mapping over just the numeric-like indices of the tuple like
"0" | "1" | "2"
, you're mapping over all the indices, includingnumber
, a mixture of all the elements. And that gives you the union you're unhappy with.The easiest change here is to explicitly map over only the numeric-like indices, by intersecting
keyof TcolTuple
with the pattern template literal type`${number}`
(as implemented in microsoft/TypeScript#40598. That removes anything that isn't a string version of a number. For example,"0" | "1" | "2" | number | "length" | "find"
when intersected with`${number}`
, gives you just"0" | "1" | "2"
.That more or less fixes it:
Personally, if I were writing this for myself, I would:
const
type parameters instead of requiring callers useconst
assertions;rowsToObjects(["a"],[0],[1])
should return[{a: 0}, {a: 1}]
and not{a: 0 | 1}[]
;I
andJ
instead ofi
andj
, keeping to the naming convention to distinguish types from variables (in{[P in K]: F<P>}
P
is a type parameter, not a variable name, sop
could be confusing).None of these are of vital importance, but it gives the output
Playground link to code