SteckRein
SteckRein

Reputation: 61

Typescript - deeply remove Object key type and inject its children

I want to deeply remove all localization keys but inject its Record Type where the localization key was. The type transformation should also work for Arrays.

For example:

Given Type:

type Given = {
 foo: string;
 bar: number;
 localizations: {
  de: {
   name: string;
   email: string;
  };
  en: {
   name: string;
   email: string;
  }
 }
 deep: Array<{
  foo: string;
  localizations: {
   de: {
    bar: string;
   };
   en: {
    bar: string;
   }
  }
 }>
}

Desired:

type Desired = {
 foo: string;
 bar: number;
 name: string;
 email: string;
 deep: Array<{
  foo: string;
  bar: string;
 }>
};

Upvotes: 0

Views: 40

Answers (1)

geoffrey
geoffrey

Reputation: 2444

You can use a mapped recursive type

type FlattenDeep<T, P extends PropertyKey> =
    // we unwrap the contents of the key `P`, here "localization"
    ( T extends { [K in P]: unknown }
        ? T[P][keyof T[P]]
        : unknown )
    // we intersect it with the object Omitting that key
    & {
    [K in keyof T as K extends P ? never : K]: // the key `P` is filtered out
        // the recursive bit
        T[K] extends unknown[] ? FlattenDeep<T[K][0], P>[]
        : T[K] extends {} ? FlattenDeep<T[K], P>
        : T[K]
    }
type Desired = FlattenDeep<Given, 'localizations'>;
type Desired = ({
    name: string;
    email: string;
} | {
    name: string;
    email: string;
}) & {
    foo: string;
    bar: number;
    deep: FlattenDeep<{
        foo: string;
        localizations: {
            de: {
                bar: string;
            };
            en: {
                bar: string;
            };
        };
    }, "localizations">[];
}

Now if you don't like the way the resulting type looks like in tooltips you can force it to compute (the two types should be equivalent, modulo some TS quirks which I don't think apply here)

type Compute<T> = unknown & { [K in keyof T]: T[K] }

type KillUnion<U> =
    (U extends any ? (k: U) => void : never) extends ((k: infer I) => void)
    ? I : never

type FlattenDeep<T, P extends PropertyKey> = Compute<
    ( T extends { [K in P]: unknown }
        ? KillUnion<T[P][keyof T[P]]>
        : unknown
    ) & {
    [K in keyof T as K extends P ? never : K]:
        T[K] extends unknown[] ? FlattenDeep<T[K][0], P>[]
        : T[K] extends {} ? FlattenDeep<T[K], P>
        : T[K]
    }
>
type Desired = FlattenDeep<Given, 'localizations'>;
type Desired = {
    name: string;
    email: string;
    foo: string;
    bar: number;
    deep: {
        bar: string;
        foo: string;
    }[];
}

Upvotes: 1

Related Questions