Type Transformations Workshop (4 exercises)
solution

Use Recursion and Mapped Types to Create a Type Helper

Here’s what the solution looks like:

As mentioned in the exercise introduction, we can recursively call DeepPartial inside of itself.

To explain how the solution works, let's simplify it by removing the first aspect of the conditional check:

Allowing Optional Properties and Recursive Calling

In the above, DeepPartial<T[K]> makes a recursive call with the value of the object being iterated over.

Note the inclusion of the ? operator. Without it, we end up creating the object again.

Including the ? allows us to essentially mark properties as optional.

Passing MyType into DeepPartial at this point shows us that types can be either what they originally were or undefined:

Note that we can now skip over letter keys:

At this point it looks like it works, but TypeScript is yelling at us in our test, because our helper is slightly imprecise.

According to our test, we don't want to allow for g to be undefined.

Handle the Array Case

We need to update the solution at this step to handle the case where T is an array. It should not be allowed to pass undefined to a member of the array.

To do this, we'll check to see if T extends Array, where the type U is inferred.

If this conditional passes, we'll say that Array should recursively call DeepPartial<U>.

What this says is as it works through MyType, when it gets to g which is an array, infer the thing inside of the array then return a new array with DeepPartial inside of it.

This won't allow undefined to be passed in. Inside, it has to be an object with h and i inside of it.

Recap

While I'm sure there are some cases that this won't handle– for example, the [K in keyof T] would be a no-op for a string, number, or boolean– this solution works great for this challenge.

This solution has a bit of hardcore TypeScript syntax. It's dense, there's barely any space, and nothing can be deleted or removed from it.

However, it does provide an illustration of the power of optional types, recursive types, and array inference.

Transcript

[0:00] Let's take a look at the solution here. This solution is kind of interesting. What you can see is, first of all, DeepPartial itself is declared up there, and then it's used inside its declaration. Similar to JavaScript functions, we can actually reuse the type helper inside itself.
[0:20] To show you how this is working, I'm actually going to delete this first aspect of the conditional check. DeepPartial here. What's going on is we're saying K in keyof T, assuming that this is an object, within calling DeepPartial on the keyed or the value of the object that we're iterating over, which is interesting.

[0:43] If we were to remove this little modifier then, then what we'd end up with is we're just creating the object again. That's kind of intriguing. All this is doing then is it's basically just making this optional in here. We end up with A string undefined, B string undefined, C string undefined.

[1:04] If we say const results is a result and let's say...Let's actually go down into the depths of this, because this does work recursively. What we're saying then is I don't need to provide a D inside here. I don't need to do any of that. I've got G inside here, which is the array too. Even inside the array, this actually works pretty well too.

[1:27] We've got the blah, blah, blah. I don't need to provide all the aspects to the elements of the array too, so why is this now yelling at me? Surely, I'm done here, right? Well, there's one little wrinkle inside here, which is we don't want to allow passing inside G. We don't want to allow passing undefined here, which this type lets me do. It's slightly imprecise here.

[1:54] Actually, we need to handle the case when T is an array. If you think about a T, because we're doing this DeepPartial thing, we basically go, OK, first of all, we call it on the outside object. Then we call it on TK. K is here. This is going to be the thing that we're accessing. We get to call on all of the values.

[2:14] On this C, then on D obviously, then E. Then it gets called on G too. We need to handle the case where G is this. You shouldn't be able to pass undefined to a member of an array here. Let's do that. We're going to say T extends any array. In fact, I'm going to use this syntax.

[2:38] Let's say an array of any. If it does, then what we're going to do is we're going to say array, and let's say...I think I might be able to pass T here, but that's imprecise because T actually represents the array here. What we want to be doing is we want to extract the thing that's inside the array and then pass that back to it.

[3:04] In fact, maybe I can just return. Oh no, right, because what I want to be doing is extracting DeepPartial front. What we're going to do is say infer U inside here, and then say array is DeepPartial U.

[3:22] What's going on there is we're saying if we get to G and we grab G, and we understand that it's an array, then we're going to infer the member of the array, the thing that's inside the array, and then return a new array with the DeepPartial inside it.

[3:41] This is actually going to not allow undefined to be passed here, because the only thing we should be able to pass is an object with H and I inside it. Wow, intense.

[3:54] We could go even further here if we had some other...I'm feeling in the back of my mind, in the depths of my heart that maybe there are some cases that this doesn't handle. For this example, this is perfectly fine because we're handling the arrays properly. We're handling the objects properly.

[4:11] When we get to a value like, let's say, T is a string or a number, then technically, you can say K in keyof T except key of string is sort of never, basically. This is like a noop when it reaches a string, or a number, or a Boolean.

[4:30] For our use case here, it does seem to be working perfectly fine. You now can't pass in undefined into this area, which is exactly what we wanted. I think we're pretty much done here.

[4:44] This is extremely hardcore like, bit of TypeScript here. This is where TypeScript can get really, really tough, which is where you have an extremely dense set of syntax, which all is like pressures and all belongs there with almost no space whatsoever, like can't delete anything, can't remove anything.

[5:07] Hopefully, this gives you a good idea of the power recursive types, also the power of this array inference, and the power of this optional type here too.