You can make this easier by using the Venn diagram
Let's say we have two sets A
and B
. All you need is the union of A - B
and B - A
. How can we get them if we only know A
and B
?
First of all we need to find two things: the union (∪
) of A
and B
and the intersection (∩
) of A
and B
. Why?
As we see in the diagram
A = (A - B) ∪ A∩B
B = (B - A) ∪ A∩B
Now we can see that
A - B = A - A∩B
B - A = B - A∩B
We need union of A - B
and B - A
: (A - B)∪(B - A) = A∪B - A∩B - A∩B
. In terms of sets, subtraction the same thing twice is useless so we can subtract only one time, so the final formula:
(A - B)∪(B - A) = A∪B - A∩B
In TypeScript the union of two types can be defined with & operator:
type Union<T1, T2> = T1 & T2;
The intersection of two types can be defined in this way (original post):
type Intersection<T1, T2> = {
[K in keyof T1 & keyof T2]: T1[K] | T2[K]
}
So all we need is use Omit to get the difference
type Diff<T1, T2> = Omit<Union<T1, T2>, keyof Intersection<T1, T2>>
Example
UPD
Sorry I didn't notice this "I tried to solve without using helper TS types like Exclude". It is not a problem we can define our type MyOmit
In the source of Omit we can see that Omit
defined as:
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>
So we need to define our MyExclude
and MyPick
to define MyOmit
as:
type MyOmit<T, K extends keyof any> = MyPick<T, MyExclude<keyof T, K>>
As it described in source we can define MyExclude
like this:
type MyExclude<T, U> = T extends U ? never : T
MyPick
we also can define as it described in source:
type MyPick<T, K extends keyof T> = {
[P in K]: T[P];
}
I know that you can say "Hey! You just copied the code from source it is not your implementation!" and you will be absolutely right. But I don't think that I must to invent a bicycle :) It is better to understand how it works and then use it anywhere
Lets start with the easiest type MyPick
:
Pick<T, K extends keyof T>
here we use K extends keyof T
to define that we can pass only the keys which are in type T
[P in K]
it works similar as in JS loop for..in, we just list object keys
T[P]
means that for each key P
we define exactly the type which defined in type T
The type MyExclude
also not so hard but it will be easier to understand on specific example. Lets say that T = 'a' | 'b' | 'c'
and U = 'a' | 'b'
Then we have to deal with 3 cases:
'a' extends 'a' | 'b' ? never : T
- yes 'a'
extends 'a' | 'b'
, so the answer is never
'b' extends 'a' | 'b' ? never : T
- yes 'b'
extends 'a' | 'b'
, so the answer is never
'c' extends 'a' | 'b' ? never : T
- no 'c'
extends 'a' | 'b'
, so the answer is 'c'
Now we have:
MyExclude<T, U> = MyExclude<'a' | 'b' | 'c', 'a' | 'b'> = never | never | 'c' = 'c'
The final code:
type Union<T1, T2> = T1 & T2;
type Intersection<T1, T2> = {
[K in keyof T1 & keyof T2]: T1[K] | T2[K]
}
type MyPick<T, K extends keyof T> = {
[P in K]: T[P];
};
type MyExclude<T, U> = T extends U ? never : T;
type MyOmit<T, K extends keyof any> = MyPick<T, MyExclude<keyof T, K>>
type Diff<T1, T2> = MyOmit<Union<T1, T2>, keyof Intersection<T1, T2>>
Example