TypeScript Type Challenge TupleToObject Walkthrough

| 2 min read

The goal of the challenge is to implement a generic type called TupleToObject, that transforms the tuple type passed to it into a object type where key and value are both elements from the tuple.

The challenge: https://github.com/type-challenges/type-challenges/blob/main/questions/00011-easy-tuple-to-object/README.md

Watch the video

An example case

For example, given the following interface, our TupleToObject should generate this type.

const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const

type result = TupleToObject<typeof tuple> // expected { tesla: 'tesla', 'model 3': 'model 3', 'model X': 'model X', 'model Y': 'model Y'}

Approach

Like in the Pick challenge, we need to create a new type based off an existing type, so therefore we can use a mapped types to do so.

Since we want our object type to contain key value pairs that both correspond to one entry in our readonly tuple, we could implement it like so:

type TupleToObject<T extends readonly any[]> = {
[Property in T]: Property
}

If this worked, it would create a new object type, with each key being Property and each value being Property, making our test cases pass.

The solution

Unfortunately this doesn't work as mapped types can only be used against union types, not readonly tuple types.

So we need to convert our readonly[] type into a union type, and then loop over this union type.

We know that given for any readonly[] we could construct it's union type by taking the type of the first element, the second, the third all the way up to the nth, like:

const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const

const TupleType = typeof tuple

type union = TupleType[0] | TupleType[1] | TupleType[2] // etc

This works as we can use index types to access the types of each element in the readonly[].

This works, but we need to know the length of our tuple upfront, instead we can use the TupleType[number] syntax to generate a union of all of the types in a readonly[] tuple.

Using T[number] to convert our readonly array into a union type, and mapping over that union looks like:

type TupleToObject<T extends readonly any[]> = {
[Property in T[number]]: Property
}

Finally, we need to constrain the type of readonly arrays that we accept. Since at the moment you can pass:

const t = TupleToObject<[[1, 2], {}]>

without the TypeScript compiler complaining, but clearly our TupleToObject type can't work on this type since [1, 2] and {} aren't valid keys in a typescript type.

We need to restrict our any[] type to be only tuples that contain elements that are valid keys. Valid keys in typescript can be string, number or symbol so our final type becomes:

type TupleToObject<T extends readonly (string | number | symbol)[]> = {
[Property in T[number]]: Property
}