Learn the different ways to filter members of TypeScript unions, depending on their member types and usecases. We'll see unions of primitives, unions of objects and discriminant unions.
Transcript
[00:00] Let's start with filtering out primitive values. Here I've got the type some garbage which includes as the name suggests some garbage Which is yes, no, just some strings, some numbers, some booleans and even an undefined value So what we expect here is that in this case, we would have only the string values so what we expected yes no since we are missing an implementation we've got the any type in TypeScript as for now and in this case we just want to filter out the number types and leave the other things apart. So there are two ways to filter primitives. And the very important thing is that whenever we try to find an intersection, like the common part across, let's say, numbers and strings or any other primitive types like booleans, symbols, begins, and so on and so forth we're going to find out that there is no overlap between them whatsoever so they are exclusive, This is very important in this case. So one way to filter out the union of primitives is basically to make an intersection with whatever we need over here so that we're making an intersection of some garbage which includes many different things and only strings.
[01:19] So that's basically the way. And the same way we can make sure that some garbage and number gets filtered down only to the numbers, which is what we expect. So this is way number one. The other way that we can use over here is that we're going to filter out using the extract type utility that is built into TypeScript which uses a conditional type. So in this case, what we're doing is on the left-hand side, we're passing a union which gets distributed on the right-hand side of the assignment operator.
[01:57] So here we are checking only one member of a union whether it does exist within the second type parameter U, if it does basically hand it over to the result, and if it doesn't, pass never so that never gets reduced to nothing because the sub results are going to be merged back into a union back again. So if you don't know what is going on here with the distributivity, check out my other video, you can see the link in the description of the video. So the other way is that we can actually make some garbage and make it a parameter of the extract and what we actually want is to extract all the strings out from the useGarbage so that in this way some garbage is going to be distributed into certain elements and if any of the elements, because here we are making, we are evaluating the condition not over the whole union, because this is the whole union on the left-hand side and just a member on the right hand side so we're evaluating whether this single member of the union is within strings if it is then basically pass it over and here we have the sub results of the merge back into union back again and here the same we're going to run the extract with some garbage and number.
[03:21] So with primitives we have two options. But this is going to work slightly different way with objects. So let me just quickly paste it and clean it up a little bit. All right, so here we have a type occupation, which is a union of objects, which includes just four members. What is important over here is that those members have slightly different properties over the object types.
[03:50] And what we need to do here is to figure out what is the region occupation so we want to have only those members of the union which include the region property so this one should get excluded This one should also get excluded since region property basically does not exist over here and these two should be included. So the correct way with dealing with objects is only to use the extract. Well, we could say that this is the safe way. And when it comes to the intersection, it could be what we want, but it doesn't have to be. Now, what is the reason?
[04:30] So we have already shown that the type test the strings and number that there is this is an empty intersection so there is no overlap however if we for instance take these two objects and we can see that there is name, experience, specialty and region, the properties of both objects are exclusive. So if we make an intersection out of them, this is not going to be empty. So let's basically see the test what we're going to have. So type intersection of objects which is going to be one and the other and we can also include the funny looking syntax that we basically start with the operator. And what we will basically see is that it's not reduced down to never because this is just going to be a contract that will need to satisfy the requirements of the first contract and of the second contract and there is no contradiction.
[05:38] So all in all primitive types have no overlap unless we're speaking about subtypes of strings, subtypes of number etc. But generally speaking primitive types are exclusive and object types, as long as they don't have the same properties with the different type, they do have some overlap. And this is what makes a difference. So all in all, if we use extract, we want to extract from occupation any type, any member that does include a region. So what we're interested in is basically something that has a region.
[06:12] What could have a region? Of course it could be an object and we need to define what could be the value of a region and we could put any unknown it doesn't make a big difference over here so if we take a look what is the region occupation we can see that specialty string region string gets in because region exists here and ID number, region number exists here so this is cool. So here those two pass. Now this name is name dentist experience 123 region is missing so this gets excluded so we could also use the t.addtsexpect error over here and yep we do have an error and over here such a member doesn't exist on the union because what we have over here is that a there is a member with a string region and a number region, but there is no member with a region that would be an object. So it doesn't matter what we actually put here.
[07:13] There is basically no member that this one could correspond to. So we expect errors in those two cases. So extract is safe in this case. However, if we provide something that is clearly wrong, so this is a spoiler, we could basically try to make the intersection. And what happens here, unfortunately, is that, by the way, we can check what is going to be the result if we just try to run it this way.
[07:45] We'll see that, okay, this one still fails. However, this one doesn't fail. So what is the reason? So let's take a look at what this wrong is. We can see that we're making an intersection of the union of four members.
[08:01] So what happens here is that the union types get distributed. Again, if you're interested in what is distribution of a union, check the link to the other video. So the first member is being intersected with the region unknown. The second member is being intersected with the region unknown, the third one and the fourth one as well. And we can see that this one being intersected with region of type string, basically changes nothing because the common part of string and number is basically the same.
[08:33] So this one would get just passed further into the result. This one would also get passed further into result. In this case, what we have over here is that name, string, experience, region. Then region of type unknown would get merged to the type because when we have an intersection of object types we basically merge their requirements so we merge their properties as long as they are not contradictory. So this is one of the case and here we would get the very same thing which is basically region of type unknown, this would get included and why does TS expect error would fail here?
[09:16] It's because this would get included because name string experience number is expected and this is included over here so we were expecting it would be excluded but it is included so this is the reason for the error over here. So let's just bring it back to normal. So what is important is that for primitives both the intersection and the extract conditional type are good and that's because primitives are exclusive and they have no overlap and when it comes to objects that might, they might have some overlap so this could be wrong. But it's not always wrong. So the final use case, let me just copy the code over here, is some occupations, a new type.
[10:09] So we've got a union with two members, and this is an object which includes the type developer, and we've got another member with type doctor just whatever properties they have and there is just an example of some folks one of them is a developer which includes the required property languages and another object which is a type doctor which includes both a specialty and works at night shifts Sorry for that And they both pass during compilation. So what we want is to get only the doctors So what we know is that certainly using the extract that would pass Obviously extract and from some occupations we need doctors so here in this case we're not interested in the existence of a property but we're interested in a certain value So in this case we expect the line 49 to fail since this is a developer. So we can also use the tsExpectError over here. So yep, this is an error. By the way, if we put a type which is too wide, then developer would get accepted.
[11:23] This would not raise an error. And for this reason, TS expect error would throw during compile time. So yeah, obviously extract is something that is safe because each member of the union is going to be, the union is going to be distributed and each member is going to be checked for the condition separately. However, What we're interested in is whether in this case we could potentially, whether we could or not, use the intersection. And the answer is yes.
[11:53] Why? Because if we try to make an intersection with type doctor, if we try to make an intersection of type doctor and type doctor, basically nothing changes. So we can just filter this one so that we do have it. However, if we want to make an intersection of two object types with contradicting properties, this is going to return never. So enter section of two object types with contradictory properties.
[12:29] Oh, now this is a very long name. So what we're trying to do is to make a type doctor object type being intersected with type developer. So what we get over here is, in short, This is an object type which needs to have a type property, but the problem is what is the common intersection of doctor and developer? This gets never, so all in all everything over here gets never. So from doing this intersection over here, Doctors survive this intersection and developer gets down, gets like reduced down to never and all in all it reduce, it reduces this to just one single member of the union.
[13:16] So just to sum up, we have two possibilities to filter the elements of the unions, either by the intersection or extract. Extract is safe because of the distributive nature of unions and intersections are safe when there is no overlap between types. Now primitives don't have the overlap between types. A distributive union which have a discriminant property, so the same property on all types with exclusive values, this is also safe to make the intersection which we can see over here. This does expect an error and it does throw the error.
[13:59] However, if we have a union of object types which is not a discriminant union then we could get into a problem because the properties which do not overlap on the types will get basically merged and this is definitely what we don't want to have. So if you don't want to care what is the solution, then basically stick to extract.