title: Typescript Generic Object Map
body: <p>Need some help finishing off a generic object.map function, my current implementation is both extremely slow on the compiler to the point where it breaks (but works) and currently i can't think of a good way to infer the ReturnType</p>
  
  <pre><code>type DeepKeyOf&lt;T extends object&gt; = {
      [K in keyof T]: T[K] extends object ? DeepKeyOf&lt;T[K]&gt; | { key: K; value: T[K] } : { key: K; value: T[K] }
  }[keyof T];
  
  function objectMap&lt;T extends object, B&gt;(obj: T, mapper: (value: DeepKeyOf&lt;T&gt;) =&gt; any): T {
      return Object.keys(obj).reduce((newObj, key) =&gt; {
          return { ...newObj, [key]: mapper(obj[key]) };
      }, {}) as any;
  }
  </code></pre>
  
  <p>This will give back a discriminated union of {key, value} object literal pairs such that you can check the key with   key === "foo" and get the typeguarded value of that key back which is what i want.</p>
  
  <p>2 problems.</p>
  
  <ol>
  <li>DeepKeyOf often stops compiler working (unperformant) even on very small interfaces</li>
  <li>Can't infer ReturnType (related to "T" on very end) and 'any' on mapper function</li>
  </ol>
  
  <p>If someone says they can solve the problem with higher-kinded types give me the pseudo code perferably with scala syntax for HKT"s and i can write out a implementation that should finish it off.</p>
  
  <p>EDIT:</p>
  
  <pre><code>type DeepKeyOf&lt;T extends object&gt; = {
      [K in keyof T]: T[K] extends object ? DeepKeyOf&lt;T[K]&gt; | { key: K; value: T[K] } : { key: K; value: T[K] }
  }[keyof T];
  interface IPerson {
      name: "susan";
      children: {
          billy: string;
          sally: Date;
      };
  }
  </code></pre>
  
  <p>will give back
  <code>{key: "name", value: "susan"} | {key: "children", value: {billy: string, sally: Date} | {key: "billy", value: string} | {key: "sally", value: Date}</code></p>
  
  <p>EDIT: This is how far iv'e gotten which is close, but it only works when all keys are handled individually if you remove the "if key == "dob"" the ReturnType comes out as not what i want because Dob is a union of the other types.</p>
  
  <p>// also i went with just making it shallow object.map to make it easier on the compiler and myself.</p>
  
  <pre><code>export type UnionToIntersection&lt;U&gt; = (U extends any ? (k: U) =&gt; void : never) extends ((k: infer I) =&gt; void)
      ? I
      : never;
  
  type Omit&lt;T, K extends keyof T&gt; = Pick&lt;T, Exclude&lt;keyof T, K&gt;&gt;;
  type ShallowKeyOf&lt;T extends object&gt; = { [K in keyof T]: { key: K; value: T[K] } }[keyof T];
  
  interface IPerson {
      name: "susan";
      children: {
          billy: number;
          sally: Date;
      };
      dob: Date;
  }
  
  type GenerateRecord&lt;Tuple extends [any, any]&gt; = Tuple extends infer SELF
      ? SELF extends [infer K, infer V] ? (K extends string ? Record&lt;K, V&gt; : never) : never
      : never;
  
  type ChangeValue&lt;T extends object, Tuple extends [keyof T, any]&gt; = Omit&lt;T, Tuple[0]&gt; extends infer SELF
      ? SELF &amp; UnionToIntersection&lt;GenerateRecord&lt;Tuple&gt;&gt;
      : never;
  
  
  function objectMap&lt;T extends object, TupleReturn extends [keyof T &amp; string, any]&gt;(
      obj: T,
      mapper: (value: ShallowKeyOf&lt;T&gt;) =&gt; TupleReturn
  ): ChangeValue&lt;T, TupleReturn&gt; {
      return Object.keys(obj).reduce((newObj, key) =&gt; {
          return { ...newObj, [key]: mapper((obj as any)[key]) };
      }, {}) as any;
  }
  
  const test = objectMap(("" as any) as IPerson, ({ key, value }) =&gt; {
      if (key === "name") {
          return [key, new Date()];
      }
      if (key === "dob") {
          return [key, "NOW STRING"]
      }
      return [key, new Date()];
  });
  </code></pre>