Search code examples
typescripttype-conversion

Typescript: how to use conditional type inference and template literal types in nested objects


In a different question I was provided with a great way to strip properties like nameDe and nameFr from a type and simply get name. Shout out @jcalz!

Now my use case got a little more complex - I have nested translated interfaces:

type TranslatedObject<T> = {
  [K in keyof T as K extends `${infer Name}${'De' | 'Fr'}` ? Name : K]: T[K];
};

interface Gimmick {
  nameDe: string;
  nameFr: string;
}

interface Car {
  nameDe: string;
  nameFr: string;
  price: number;
  gimmicks: Gimmick[];
}

const tcar: TranslatedObject<Car> = {
  name: 'Car name',
  price: 69,
  gimmicks: [{ name: 'seat ventilation' }], // Error: Type '{ name: string; }' is not assignable to type 'Gimmick'.
};

Notice how name: 'Car name', works properly but the nested Gimmick with value { name: 'seat ventilation' } does not. Stackblitz example


Solution

  • The solution is pretty straightforward. Just call TranslatedObject recursively if the property is an object:

    type TranslatedObject<T> = {
      [K in keyof T as K extends `${infer Name}${'De' | 'Fr'}`
        ? Name
        : K]: T[K] extends object ? TranslatedObject<T[K]> : T[K];
    };
    

    Usage:

    const tcar: TranslatedObject<Car> = {
      name: 'Car name',
      price: 69,
      gimmicks: [{ name: 'seat ventilation' }], // no error
    };
    

    playground