Trident D'Gao
Trident D'Gao

Reputation: 19690

A way to get an interface with sub-sub-sub-property removed in TypeScript

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

Answers (2)

jcalz
jcalz

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

adz5A
adz5A

Reputation: 2032

If you know precisely the path to omit I guess you could do this using an intersection type like in this example. The idea is to intersect a type which does not accept a value at the specified path with the original type A.

Upvotes: 0

Related Questions