typescripttypescript-generics

How do I get all the properties and sub properties of an object as a type?


I'm using the i18next package to manage text translation in a React typescript project. I want guarantee that the string path I provide to the t function actually exists, so I'm creating a type of possible paths and a wrapper function. The code (for the type) would look something like this:

const enTranslation = {
  a: {
    a1: "a1",
    a2: "a2",
  },
  b: {
    b1: "b1",
    b2: "b2",
  },
  c: "c",
};

type Join<K extends string, P extends string> = P extends "" ? `${K}` : `${K}.${P}`;

type Paths<T> = T extends object ? Join<keyof T & string, Paths<T[keyof T]>> : ""

type TranslationPath = Paths<typeof enTranslation>;

However, it doesn't work as I intended it to because the 2 keyof T's in the Paths<T> type are different. Because of that, the following instance of the type is possible (which would be an invalid path in this case, as there is no b1 property in the a property of the object enTranslation):

const test: TranslationPath = "a.b1";

Therefore, my questions are:

  1. How do I refer to the same keyof T in both places as one, is there a "type variable" mechanism that allows that?
  2. Is there another way to write the definition of the Paths<T> type?
  3. Is there another way to achieve my general goal?

Solution

  • I think you don't need the intermediate paths, since they refer to objects, not to strings, so:

    Playground

    export type Paths<T> = T extends object ? { [K in keyof T]:
        `${Exclude<K, symbol>}${Paths<T[K]> extends never ? "" : `.${Paths<T[K]>}`}`
    }[keyof T] : never