Home > Software design >  Typescript - deeply remove Object key type and inject its children
Typescript - deeply remove Object key type and inject its children

Time:01-07

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;
 }>
};

CodePudding user response:

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;
    }[];
}
  • Related