undefined
undefined

Reputation: 6844

Typescript how to write nested keyof

I have the following code;

class Transform<T> {
  constructor(private value: T) {}
}


class Test<T extends object> {
  constructor(private a: T) {}

  transform(): { [K in keyof T]: Transform<T[K]> } {
    // functionality omitted
    return this.a as any;
  }
}

const test = new Test({
    a: '',
    b: {
      c: '',
      d: {
          v: ''
      }
    }
})

const transformed = test.transform();

The issue is that the transform method return type is:

const transformed: {
    a: Transform<string>;
    b: Transform<{
        c: string;
        d: {
            v: string;
        };
    }>;
}

But what I want is that every key will be of type Transform:

const transformed: {
    a: Transform<string>;
    b: Transform<{
        c: Transform<string>;
        d: Transform<{
            v: Transform<string>;
        }>;
    }>;
}

There is a way to achieve it with TS?

Upvotes: 0

Views: 120

Answers (1)

Karol Majewski
Karol Majewski

Reputation: 25790

Sure. Try a conditional recursive type like this:

type Transformed<T> = {
  [K in keyof T]:
    T[K] extends object
      ? Transform<Transformed<T[K]>>
      : Transform<T[K]>
};

Use it as the return type for transform().

class Test<T extends object> {
  constructor(private a: T) {}

  transform(): Transformed<T> {
    // functionality omitted
    return this.a as any;
  }
}

The result:

/**
 * @returns
 *
 * {
 *    a: Transform<string>;
 *    b: Transform<{
 *        c: Transform<string>;
 *        d: Transform<{
 *            v: Transform<string>;
 *        }>;
 *    }>;
 * }
 *
 */
const transformed = test.transform();

Note: Some IDEs (like Visual Studio Code) may not show the expanded return type. If you want to make sure that my solution is indeed what you are looking for, use a helper type like Compact so that the return type appears in full glory.

/**
 * @see https://github.com/microsoft/TypeScript/issues/14829#issuecomment-320754731
 */
type Compact<T> = {} & { [P in keyof T]: T[P] };

type Transformed<T> = Compact<{
  [K in keyof T]:
    T[K] extends object
      ? Transform<Transformed<T[K]>>
      : Transform<T[K]>
}>;

Now it will look exactly like what you asked for. Hope that helps!

Upvotes: 1

Related Questions