Reputation: 19690
Say I have
interface A {
b: {
c: {
d: string;
e: number;
}
}
}
I would like to get interface A_no_E
which is the same interface as A
but without A.b.c.e
interface A_no_E {
b: {
c: {
d: string;
}
}
}
Is there a handy way of getting A_no_E
via advanced types of TypeScript without having to redeclare the whole thing prop-by-prop?
Upvotes: 1
Views: 154
Reputation: 328057
Hmm, I suppose you could declare your own DeepOmit
mapped and conditional type function that walks down through a tuple of keys... something like this:
// Tail removes the first element from a tuple type
// so Tail<[string, number, boolean]> is [number, boolean]
type Tail<L extends any[]> =
((...l: L) => void) extends ((h: infer H, ...t: infer T) => void) ? T : never;
type DeepOmit<T, K extends Array<keyof any>> =
K extends [] ? T : K extends [infer P] ? Pick<T, Exclude<keyof T, P>> : {
[P in keyof T]: P extends K[0] ? DeepOmit<T[P], Tail<K>> : T[P]
}
And then you can do
type A_no_E = DeepOmit<A, ['b', 'c', 'e']>;
This, if you use IntelliSense, evaluates to the somewhat ugly-looking
type A_no_E = {
b: {
c: Pick<{
d: string;
e: number;
}, "d">;
};
}
Which is equivalent to what you want:
declare const a_no_e: A_no_E
a_no_e.b.c.d; // string
a_no_e.b.c.e; // error, property e does not exist
EDIT: alternate definition of DeepOmit
which produces a less ugly IntelliSense:
type Tail<L extends any[]> =
((...l: L) => void) extends ((h: infer H, ...t: infer T) => void) ? T : never;
type DeepOmit<T, K extends Array<keyof any>, KT extends keyof T = Exclude<keyof T, K[0]>> =
K extends [] ? T : K extends [infer P] ? { [Q in KT]: T[Q] } : {
[P in keyof T]: P extends K[0] ? DeepOmit<T[P], Tail<K>> : T[P]
}
Now A_no_E
is shown in IntelliSense as:
type A_no_E = {
b: {
c: {
d: string;
};
};
}
Hope that helps.
Upvotes: 2