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
Record
that 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 toPick
at all?We simplify the type to just
= METHODS & MORE
and we replaceSelectMethods<any>
withSelectMethods<Partial<All>>
sinceany
isn't really a valid input and we get this:This should resolve your issues.
Typescript Playground Link