So I have the following interfaces & types:
interface A {
A(): void;
}
interface B {
B(): void;
}
interface C {
C(): void;
}
type All = A & B & C;
and i want to extract a partial set of the methods AND create another nesting level which can do the same thing recursively: [typescript version 4.1.0]
type SelectMethods<
METHODS extends Partial<All>,
MORE extends Record<string, SelectMethods<any>> = {}
> = Pick<All, Extract<keyof All, keyof METHODS>> & MORE;
- Taking a partial set of
All - Picking only the relevant selected methods
- Merging it with a
Recordthat for each property do the same
this allows me to do the following, which works great:
const obj: SelectMethods<A & B, { x: SelectMethods<A & B & C> }> = {
A() {
},
B() {
},
x: {
A() {
},
B() {
},
C() {
}
}
};
But when removing the C (or any of the others) in the inner SelectMethods it stop working:
[error appears for the type literal between the stars]
const obj: SelectMethods<A & B, **{ x: SelectMethods<A & B> }**> = { // Property 'C' is missing in type 'Pick<All, "A" | "B">' but required in type 'Pick<All, "A" | "B" | "C">'.
A() {
},
B() {
},
x: {
A() {
},
B() {
}
}
};
things i've tries so far:
- conditional types to try proving to the compiler it's ok:
type SelectMethods<METHODS extends Partial<All>, MORE extends Record<string, SelectMethods<any>> = {}> =
Pick<All, Extract<keyof All, keyof METHODS>> & (
MORE extends Record<string, infer DEEP> ?
DEEP extends SelectMethods<infer SUBMETHODS> ?
SUBMETHODS extends Partial<All> ?
MORE
: never
: never
: never
);
but i'm getting TS2589: Type instantiation is excessively deep and possibly infinite.
any ideas? :)
The problem is with
Let's make that it's own type so that we can play with it:
This works fine on the first level.
type AB = MethodPick<A & B>is just A & B.But what is the actual type of
SelectMethods<any>akaSelectMethods<Partial<All>>? You might think that it would be a partial selection of methods, but it's not. It actually requires that A, B, and C are all set. This is because when we do ourMethodPick, we take the keys of what we are picking and get those methods. But the flaw is we haven't checked if those keys are required.keyof Partial<All>is'A' | 'B' | 'C', sotype P = MethodPick<Partial<All>>is A & B & C. That meansSelectMethods<any>is A & B & C where all three methods are required.So the above statement means that the values of this record must extend
All, notPartial<All>. And that's not what we want.If we already know that
METHODS extends Partial<All>, why do we need toPickat all?We simplify the type to just
= METHODS & MOREand we replaceSelectMethods<any>withSelectMethods<Partial<All>>sinceanyisn't really a valid input and we get this:This should resolve your issues.
Typescript Playground Link