title: Named Type association with Typescript
body: <p>I have a type that associates names to types by mean of a Mapping Object Type <code>TMap</code></p>
  
  <p>it aims to feed a handler function with an association of one of the named types with a correspondent typed value </p>
  
  <pre><code>interface Thing&lt;TMap extends { [k: string]: any }&gt; {
    get&lt;T extends keyof TMap&gt;(handler: (v: TMap[T], t: T) =&gt; unknown): unknown
  }
  </code></pre>
  
  <p>for example, say the Mapping Object type to be:</p>
  
  <pre><code>interface Types {
    num: number,
    dat: Date,
    str: string
  }
  </code></pre>
  
  <p>and an instance of Things:</p>
  
  <pre><code>declare const thing: Thing&lt;Types&gt;
  </code></pre>
  
  <p>using the <code>get</code> method to get a couple of value, type works but loses type association when checking the type: </p>
  
  <pre><code>thing.get((v, t) =&gt; {
    // v: string | number | Date
    // t: "num" | "dat" | "str"
    if (t === 'num') {
      //  v: string | number | Date
      v
    } else if (t === 'dat') {
      //  v: string | number | Date
      v
    } else if (t === 'str') {
      //  v: string | number | Date
      v
    }
  })
  </code></pre>
  
  <p>I managed a tricky workaround to fix that:</p>
  
  <pre><code>type Caster&lt;TMap extends { [k: string]: any }&gt; =
    &lt;T extends keyof TMap&gt;(
      v: TMap[keyof TMap],
      t: keyof TMap,
      isOfType: T
    ) =&gt; v is TMap[T]
  
  declare const caster: Caster&lt;Types&gt;
  
  thing.get((v, t) =&gt; {
    // v: string | number | Date
    // t: "num" | "dat" | "str"
    if (caster(v, t, 'num')) {
      //  v:  number 
      v
    } else if (caster(v, t, 'dat')) {
      //  v: Date
      v
    } else if (caster(v, t, 'str')) {
      //  v: string
      v
    }
  })
  </code></pre>
  
  <p>How to properly declare <code>Thing</code> and <code>Thing.get</code> to keep type association avoiding tricky hacks?</p>
  
  <p>[edit]
  check it out on <a href="https://www.typescriptlang.org/play/#src=interface%20Thing%3CTMap%20extends%20%7B%20%5Bk%3A%20string%5D%3A%20any%20%7D%3E%20%7B%0D%0A%20%20get%3CT%20extends%20keyof%20TMap%3E(handler%3A%20(v%3A%20TMap%5BT%5D%2C%20t%3A%20T)%20%3D%3E%20unknown)%3A%20unknown%0D%0A%7D%0D%0Adeclare%20const%20thing%3A%20Thing%3CTypes%3E%0D%0A%0D%0A%0D%0Ainterface%20Types%20%7B%0D%0A%20%20num%3A%20number%2C%0D%0A%20%20dat%3A%20Date%2C%0D%0A%20%20str%3A%20string%0D%0A%7D%0D%0A%0D%0Athing.get((v%2C%20t)%20%3D%3E%20%7B%0D%0A%20%20if%20(t%20%3D%3D%3D%20&#39;num&#39;)%20%7B%0D%0A%20%20%20%20%2F%2F%20%20v%3A%20string%20%7C%20number%20%7C%20Date%0D%0A%20%20%20%20v%0D%0A%20%20%7D%20else%20if%20(t%20%3D%3D%3D%20&#39;dat&#39;)%20%7B%0D%0A%20%20%20%20%2F%2F%20%20v%3A%20string%20%7C%20number%20%7C%20Date%0D%0A%20%20%20%20v%0D%0A%20%20%7D%20else%20if%20(t%20%3D%3D%3D%20&#39;str&#39;)%20%7B%0D%0A%20%20%20%20%2F%2F%20%20v%3A%20string%20%7C%20number%20%7C%20Date%0D%0A%20%20%20%20v%0D%0A%20%20%7D%0D%0A%7D)%0D%0A%0D%0A%0D%0Atype%20Caster%3CTMap%20extends%20%7B%20%5Bk%3A%20string%5D%3A%20any%20%7D%3E%20%3D%0D%0A%20%20%3CT%20extends%20keyof%20TMap%3E(%0D%0A%20%20%20%20v%3A%20TMap%5Bkeyof%20TMap%5D%2C%0D%0A%20%20%20%20t%3A%20keyof%20TMap%2C%0D%0A%20%20%20%20isOfType%3A%20T%0D%0A%20%20)%20%3D%3E%20v%20is%20TMap%5BT%5D%0D%0A%0D%0Adeclare%20const%20caster%3A%20Caster%3CTypes%3E%0D%0A%0D%0Athing.get((v%2C%20t)%20%3D%3E%20%7B%0D%0A%20%20if%20(caster(v%2C%20t%2C%20&#39;num&#39;))%20%7B%0D%0A%20%20%20%20%2F%2F%20%20v%3A%20%20number%20%0D%0A%20%20%20%20v%0D%0A%20%20%7D%20else%20if%20(caster(v%2C%20t%2C%20&#39;dat&#39;))%20%7B%0D%0A%20%20%20%20%2F%2F%20%20v%3A%20Date%0D%0A%20%20%20%20v%0D%0A%20%20%7D%20else%20if%20(caster(v%2C%20t%2C%20&#39;str&#39;))%20%7B%0D%0A%20%20%20%20%2F%2F%20%20v%3A%20string%0D%0A%20%20%20%20v%0D%0A%20%20%7D%0D%0A%7D)%0D%0A" rel="nofollow noreferrer">TSPlayground</a> </p>