2

As template literal types are now supported in TypeScript, is it possible to have something like the following?

interface Data {
  a: number;
  b: {
    c: number;
    d: number;
  }
}

type OmitDeep<T, K> = ...

type WithoutC = OmitDeep<Data, 'b.c'>

Where WithoutC will be inferred as:

interface WithoutC {
  a: number;
  b: {
   d: number
  }
}
Deftomat
  • 629
  • 6
  • 15

2 Answers2

5

You can use this type to map dotted path to an array path:

type UnDot<T extends string> =
    T extends `${infer A}.${infer B}` ? [A, B] : ''

type ProperyArray = UnDot<'a.b'>; // ["a", "b"]

And then use this answer to remove nested properties:

Types for deleting object at specific nested path

Edit for any number of nesting levels:

type UnDot<T extends string> =
    T extends `${infer A}.${infer B}`
    ? [A, ...UnDot<B>]
    : [T];
Roberto Zvjerković
  • 9,657
  • 4
  • 26
  • 47
1

In addition to Roberto's wonderful answer, the full solution looks like this one:

type Tail<T extends any[]> = ((...t: T) => void) extends ((
    h: any,
    ...r: infer R
) => void)
    ? R
    : never;

type DeepOmit<T, Path extends string[]> = T extends object ?
    Path['length'] extends 1 ? Omit<T, Path[0]> : {
        [K in keyof T]: K extends Path[0] ? DeepOmit<T[K], Tail<Path>> : T[K];
    } : T;

type UnDot<T extends string> =
    T extends `${infer A}.${infer B}`
    ? [A, ...UnDot<B>]
    : [T];

type Nested = {
    a: number;
    b: {
        c: number;
        d: number;
    }
};

type Omitted =  DeepOmit<Nested, UnDot<'b.c'>>

const foo: Omitted = {a: 1, b: { c: 1, d: 2 } }; // error
                                 ~~~~

Typescript playground

glinda93
  • 7,659
  • 5
  • 40
  • 78