I am attempting to construct a generic mapped type that accepts 2 type arguments and returns another object with the intersection of their keys while excluding any properties that have no type overlap. In other words, I would like this generic type SharedProperties<A, B>
to pass the following test:
type A = { x: string; y?: string };
type B = { x: number; y?: string; z: string };
type C = { y: string };
type D = { y: string | number | boolean };
type AB = SharedProperties<A, B> // => { y?: string }
type BC = SharedProperties<B, C> // => { y: string }
type CD = SharedProperties<C, D> // => { y: string }
My initial attempt was as follows:
type SharedProperties<A, B> = OmitNever<
{
[K in keyof A & keyof B]: A[K] extends B[K]
? A[K]
: B[K] extends A[K]
? B[K]
: never;
}
>;
where OmitNever
is a type that simply removes properties whose type is never
.
However, the above approach didn't cover my use-case because I noticed that the ?
and readonly
property modifiers were being lost upon evaluation of the mapped type. I did some further digging and found out that the property modifiers are only preserved if your mapped type is homomorphic. See this answer.
It seems the easiest way for the compiler to infer that a mapped type is homomorphic is for the mapped key to have form [K in keyof SOMETHING]
. Thus, I modified the above to look like this:
type SharedProperties<A, B> = OmitNever<
{
[K in keyof (A & B)]: A[K] extends B[K]
? A[K]
: B[K] extends A[K]
? B[K]
: never;
}
>;
I assumed this would work, since if there was any arbitrary key K'
that existed in A
but not in B
, the conditional types should resolve to never
and just get filtered out.
This is where I ran into my second problem: if K
didn't exist on A
, then for whatever reason, A[K]
would resolve to any
instead of never
. This means the conditional type would never resolve to never
, and I was just getting an object similar to A & B
.
Finally, I was forced add an ugly as
clause to the mapped type keys:
type SharedProperties<A, B> = OmitNever<
{
[K in keyof (A & B) as K extends keyof A ? (K extends keyof B ? K : never) : never]: A[K] extends B[K]
? A[K]
: B[K] extends A[K]
? B[K]
: never;
}
>;
My problem with the above generic is that, although it works, the readability is greatly hindered because I'm basically repeating myself here. I only want to include properties that are shared between two objects. As I see it, the root of this problem is that if a key K
does not exist in some object A
, A[K]
has type any
instead of type never
, which totally breaks my conditional types.
Is there a compiler option to force unknown object properties to resolve to never
instead of any
? I have the --strict
flag enabled, and while it throws a type error, it still resolves to any
.