Selective Remapping with Conditional Types and Template Literals
It’s best to start simple when doing key remapping.
We’ll start this solution by mapping over the keys of the generic
T to give us back the object that we passed in:
T[K] will probably not change, since whatever we put in there will correspond to the values of what we pass in.
In order to keep things clean, we’ll create a helper type called
SearchForId that will use a string template to look for
"Id" that is surrounded by strings of any length:
Now we can use a conditional type inside of the mapped type.
For every key of the object, we’ll check if it extends
SearchById. If it does, it will be included. Otherwise, the
never type will be used.
never as the else case, any keys that don’t extend
SearchById will not be included in the
[0:00] Whenever you're doing key remapping like this, I like to start from a really simple base, which is K in keyof T is T [K] . We know now that OnlyIds Example, for instance, if we pull this out into Result, OnlyIdKeys Example, what this is going to do is it's going to just return me the object that I parsed in. This is a really nice base to work from from map type.
[0:25] I know that this probably is not going to change, because whatever we put into organizationId, or id, or groupId, we're going to want those in the values that comes out at the end. This T [K] is probably not going to change, but I need to find a way of restricting the members that come through. In other words, the keys that come through.
[0:46] Keyof T, if we think about it, keyof Example is going to be a union or name, age, id, organizationId, or groupId. I need to find a way to exclude those that don't match a certain type. The type I'm looking for, let's say if we have a type SearchForId, this is going to be, let's say...
[1:10] We're going to want to allow anything inside here that has an id or an Id like this, like a camel case version, and then at the start, we're going to want to allow a string, and at the end, we're going to want to allow a string.
[1:22] This means we can go SearchForId. This means age is not going to be allowed, because it doesn't contain an ID. Id is going to be allowed, because string can be empty -- we've seen this parsing before -- and organizationId is going to be allowed, because it's got the uppercase Id in.
[1:41] That's the idea here. We've got this now search, which is really good. We need to find somehow a way to restrict the keys to only the things that match this.
[1:52] The way you can do this is with a conditional type. You can say as K, which is the key that we're currently iterating over, we can extend SearchForId. If it does, we return K. If it doesn't, what are we going to return?
[2:07] We can't return undefined, because undefined isn't valid as a key. We can actually return never here. What never does in this situation, if we just say as never, then it's actually not going to allow any of the keys through, and we're going to end up with an empty object.
[2:26] In this position, never actually says don't include this in the object. This is one of the many, many use cases for never. That's why we would return it when it doesn't fit in the SearchForIds. If we were to change this, for instance, we can inline this as well if we want to just for fun.
[2:43] Let' change this to organization for something. If we just have organization here, then what we end up with is only the organizationId. This SearchForId, this is the case that we're checking for. If we return it, then we return K here, and so we remap it to K. Parsing never into this slot means that it just gets excluded from the object in the end.
[3:10] This is a really, really neat way to do this kind of object key exclusion.