title: Recursive Type Mapping in Typescript
body: <p>I'm trying to extract a type mapping from an object containing native type validation function so for example, if I have the object</p>
  <pre><code>const test  = {
    Test: {
      Test1: {
        Test2: SimpleStringValidator //return type is string or undefined but input can be anything
      },
    }
  }
  </code></pre>
  <p>I want to extract the type</p>
  <pre><code>type Extracted = {
    Test: {
      Test1: {
        Test2: string
      }
    }
  }
  </code></pre>
  <p>Towards this end I wrote the following code to extract the return types of an object containing a set of nested validation functions</p>
  <p>Sample.ts</p>
  <pre><code>
  export type Validator&lt;T&gt; = NativeTypeValidator&lt;T&gt; | ObjectValidator&lt;T&gt;
  
  export type NativeTypeValidator&lt;T&gt; = (n: any) =&gt; T | undefined
  export type ObjectValidator&lt;O&gt; = {
    [K in keyof O]: Validator&lt;O[K]&gt; 
  }
  
  //native validators
  export const SimpleStringValidator:NativeTypeValidator&lt;string&gt; = (val) =&gt; typeof(val) === &quot;string&quot; ? val : undefined
  
  //object validator function
  export const ObjValidator = &lt;V&gt;(validatorObj: ObjectValidator&lt;V&gt;) =&gt; (o:any):V =&gt;{
    let result = {} as V;
    //we can only validate objects
    if (typeof (o) !== &quot;object&quot;) { return undefined; }
    const validatorKeys = Object.keys(o) as [keyof ObjectValidator&lt;V&gt;]
    validatorKeys.forEach((validatorKey) =&gt; { 
      const objValue = o[validatorKey] as V[keyof V];
      const objectValidator = validatorObj[validatorKey]
      if (!objectValidator) { return undefined } //do nothing if no validator exists for the key in o
      //figure out if we have a nested object validator or a native validator at the corresponding key of validatorObj
      if (typeof (objectValidator) === &quot;object&quot;) {
        result[validatorKey] = ObjValidator(objectValidator as ObjectValidator&lt;V[keyof V]&gt;)(objValue)
      }
      else {
        const nativeValidator = objectValidator as NativeTypeValidator&lt;V[keyof V]&gt;;
        result[validatorKey] = nativeValidator(objValue)
      }
    })
    return result;
  }
  
  export const test  = {
    Test: {
      Test1: {
        Test2: SimpleStringValidator
      },
    }
  }
  
  export const validatorFunc = ObjValidator(test);
  export const outputExample = validatorFunc({
    Test: {
      Test1: {
        Test2: &quot;hi&quot;
      },
    }
  })
  
  outputExample.Test.Test1.Test2 = &quot;1&quot;;
  outputExample.Test.Test1.Test2 = 1; //vs code intellisense complains because needs to be type string
  </code></pre>
  <p>In my case intellisense  auto-completes the nested property Test2 and has the type as string but when I create the type declaration files the type information is different and I can't export this generated type to other projects correctly. Specifically it sets the type of property Test1 to any. The type I'm looking to generate is the output of validatorFunc and the type of outputExample in the code above.</p>
  <p>Generated sample.d.ts</p>
  <pre><code>export declare type Validator&lt;T&gt; = NativeTypeValidator&lt;T&gt; | ObjectValidator&lt;T&gt;;
  export declare type NativeTypeValidator&lt;T&gt; = (n: any) =&gt; T | undefined;
  export declare type ObjectValidator&lt;O&gt; = {
      [K in keyof O]: Validator&lt;O[K]&gt;;
  };
  export declare const SimpleStringValidator: NativeTypeValidator&lt;string&gt;;
  export declare const ObjValidator: &lt;V&gt;(validatorObj: ObjectValidator&lt;V&gt;) =&gt; (o: any) =&gt; V;
  export declare const test: {
      Test: {
          Test1: {
              Test2: NativeTypeValidator&lt;string&gt;;
          };
      };
  };
  export declare const validatorFunc: (o: any) =&gt; {
      Test: {
          Test1: any;
      };
  };
  export declare const outputExample: {
      Test: {
          Test1: any;
      };
  };
  </code></pre>
  <p>I need to use the root validator object as the source for my extracted type looking for a solution where the generated declaration file has the correct type. I have this code up on Typescript playground and it shows the difference between autocomplete and what gets put into the .d.ts file.</p>
  <p><a href="https://www.typescriptlang.org/play?strictNullChecks=false&amp;experimentalDecorators=false&amp;target=6&amp;jsx=0&amp;ts=4.0.0-beta#code/FAUwHgDg9gTgLgAjgTwiBA1AhgGwJYAmWcsAPACoB8CAvAgHLF4BuI5qI2%20RJMF1AHwQB5AEYArEAGM4XQsTJVgoSLEQo0DJq3Zo5PRdToAKAHYAuBFlPIAlLWrkEQgK6mCIAGZ5TIAiuh4JA4RCWlZXHleUmEjBABvYAQEAG0AaQQfBABrEGQoTxEAXUt9BT5hdKLqYABfZQB6BtNtdGZIgxgAZwC1BCkoUy7EAGU8AFsIHBARuBgfAHMy3nNGOBY2DmWyYfnTBbjjdpx7GmoNEAKj3FOaOgAiXcX7hAB%20BGOESzcPb19-YBNBBNKBhGQfDrlBCeNwyPCDXpBAZDRBicTbGC0BCkDCUa7ccpoyxo8IYnGUU7UYxQczWOzmDAORLJaaIGAgLouHCIOjxWpWLqYADcSWBDQA7ugpNYEIMcMgIQS4OhQZIZD1knhCsYLgUENT7ABCO4Ie6q8L3ezxBDsuAuGCmBA-Lw%20PxChD1ZLI4aKqKwNJ5QV0EkyAB0uWQXQNAtSEb1IYiSrIuKKouOfpgAcjoc8sAAolgpAALYz4jNZykJBCir2DH2qrgudB0KApdOdLNFGMYFJxwoYIoi5K1lGysGJjNY9uEiRtyG8Ts1zLaw3mmQYq02kB2h1O9wu-4esUEKAIUxQOBFxbLs%20n6e8BDgPDDQW5zGX9ARzKOqBLpreBZ7RVFxEC1BBJQQIssFYKwzw5ZUCDHNVEHvWBZUxLAz1aX1OisdQiylWB2S6aB3GvL89VQmA0SXMCdQ4PVqXHDdaBNM1x0tBIl2SYiuTgOck0zPIu2DCQMSY5CMRjBMyR7PtMGqWwJMbEBbCXT1hxAHAunQZlhxHH0WnWVgpJbZj5zQrBBTWDZdE4Cy%20DkvI9QHSgh30rdOW5ATy2ErEjI2cSG1wJs1P0jTajCrcd0dXjuRFepEUQb11Hg5JeVFch4MsPTkiy4YAEYcu4hB8rgAAmSwxkmaZZj2JYHPUgAaUV6kS8BAmSusUIcgAxWEsTRcTlWGWwRQ6voUtlECIBAvMwCwGrmxw8p%20tMKRjD0sriv0sqiq4jzSvgyrTSve5mtauo1OAKAZrmhaltDMqnvggqXuGcqsXuAr7hFW64FmuB5sWqYQHeuBwbesrProAr3SaZhBQGDxv2VHB8B0oZCJqrAfEFURpCwFwdLgvxBRIBACeCTQfDgYAgA" rel="nofollow noreferrer">TypescriptPlayground example</a></p>