Use TypeScript conditional types to create a reusable Flatten type

Rares Matei
InstructorRares Matei

Share this video with your friends

Send Tweet
Published 4 years ago
Updated 3 years ago

Generics can be a useful mechanism for transforming one type into another. In this lesson we will look at how we can use them to create different types for flattening array and object types (extracting the types of their boxed values). We will then look at how we can use conditional types to create a single multi-purpose Flatten type, that can dynamically chose a flattening strategy based on the type passed into it.

Instructor: [00:00] Here, I have an array that contains just numbers. The type of this array is, as you might guess, array of numbers. Let's say I want to build a type that flattens array types.

[00:11] Whatever array type I pass into it, it should return me the types of all the values contained in that array. In this case, it should return me just number, because there's nothing else other than a number inside this array.

[00:24] I'll call it flatten array, and it needs to accept the generic parameter key that's going to extend an array. It will query whatever array type is passed in by number, and thus return the type of the contained values.

[00:38] I'm using number, because the indexes of all arrays are numbers. Now if I try this here and I pass in whatever type my numbers array is, and if I hover over it, sure enough, I can see that this is of type number. Awesome, that worked.

[00:54] This type won't really work with objects, because objects can be indexed by much more than numbers. They can also be indexed by strings. I'll need to create a more powerful type. I'll call it flatten object, and the generic parameter this time will need to extend an object.

[01:11] Instead of always querying this type by numbers, I'll grab all of the keys of the type and then query the type by each one of its keys. Given that key, in my case, will be whatever type this object literal is, when I call key of key T, it will return me the set of ID or name.

[01:31] Then, if I try to query the type T by that set of ID or name, that's going to be equivalent to whatever T of ID returns, or whatever T of name returns. In my case, that's equivalent to number, because the value of the ID property is a number or string, because the value of the name property is a string, Jonathan.

[01:52] If we try it out here, and we pass it whatever type my object is, we can see that this type is now either a number or a string. Perfect. Since we now have this more powerful type that queries by all of the keys of a type, it should work for arrays as well, right? Let's try it.

[02:11] I'm just going to replace my array, type with my flatten object type. If we hover over it, we can see that we get this really weird type back. These are all of the methods on the JavaScript array prototype, which we don't really care about.

[02:25] We only want the types of the values accessible by the number indexes. We need to bring this back, because if something is an array, we only want to index by numbers. That's one problem.

[02:36] We're left with these two types here and we have to know when to use each. Then, there's another problem. We should be able to flatten flat types as well, like this Boolean here. We wouldn't get much out of it, because a flatten Boolean is still a Boolean. It would still be nice to have something that's generic enough that it would work with any type.

[02:54] We can try this. If I try to pass in a Boolean in here, TypeScript is going to throw an error. This is where conditional types are super handy. They allow me to build a single, all-purpose, and need type called flatten, which will accept anything.

[03:11] It's first going to check if the passed in type extends an array. If it does, then I just want to query by number. Then, it's going to check if the passed in type extends an object. If it does, it's going to query it by all of its keys.

[03:28] Finally, if it's none of those, that means it's already a flat type and it's just going to return it. Now, I'm just going to remove these, because we shouldn't need them anymore. I'll just replace all of them in here with my new type that I've created.

[03:42] If I hover over my flattened array, I see I get number back. Awesome. If I hover over the object, I can see I get strings or numbers back. Perfect. The Boolean not only passes compilation now, but if I hover over it, I get not just Boolean back, but the more specific true literal type.