title: How to type-annotate a TypeScript function that maps object properties?
body: <h1>Background</h1>
  
  <p>Let's say I want to load an object from this JSON:</p>
  
  <pre class="lang-json prettyprint-override"><code>{
    "dateStringA": "2019-01-02T03:04:05",
    "dateStringB": "2019-01-03T04:05:06",
    "nonDateString": "foobar",
    "someNumber": 123
  }
  </code></pre>
  
  <p>So the two properties <code>dateStringA</code> and <code>dateStringB</code> should actually be of type <code>Date</code>, but since JSON does not know a type <code>Date</code> it is a <code>string</code> and needs to be converted. So an option might be to write a simple mapping function that converts the the properties like this in plain old JavaScript:</p>
  
  <pre class="lang-javascript prettyprint-override"><code>function mapProperties(obj, mapper, properties) {
    properties.forEach(function(property) {
      obj[property] = mapper(obj[property]);
    });
    return obj;
  }
  var usefulObject = mapProperties(
    jsonObject,
    function(val) {return new Date(val);},
    'dateStringA',
    'dateStringB'
  );
  </code></pre>
  
  <h1>The question</h1>
  
  <p>The above works fine, but now I want to do the same in TypeScript and of course I would like to add as many type checks as possible. So in best case I would like to get the following result:</p>
  
  <pre class="lang-typescript prettyprint-override"><code>// setup
  const value = {dateStringA: '2019-01-02T03:04:05', dateStringB: '2019-01-03T04:05:06', nonDateString: '', someNumber: 123};
  const result = mapProperties(value, (val: string): Date =&gt; new Date(val), 'dateStringA', 'dateStringB');
  
  // --- TEST ---
  
  // dateStringA &amp; dateStringB should be dates now:
  result.dateStringA.substr; // should throw compile error - substr does not exist on type Date
  result.dateStringB.substr; // should throw compile error - substr does not exist on type Date
  result.dateStringA.getDate; // should be OK
  result.dateStringB.getDate; // should be OK
  
  // nonDateString is still a string
  result.nonDateString.substr; // should be OK
  result.nonDateString.getDate; // should throw compile error - getDate does not exist on type string
  
  // someNumber is still a number
  result.someNumber.toFixed; // should be OK
  
  // call not possible on properties that do not exist:
  mapProperties(value, 'doesNotExist'); // should throw compile error
  
  // call not possible on properties not of type string:
  mapProperties(value, 'someNumber'); // should throw compile error
  </code></pre>
  
  <h1>What I have tried so far:</h1>
  
  <p>This is the best I got by myself:</p>
  
  <pre class="lang-typescript prettyprint-override"><code>type PropertyNamesByType&lt;O, T&gt; = { [K in keyof O]: O[K] extends T ? K : never }[keyof O];
  type OverwriteType&lt;T, K extends keyof T, N&gt; = Pick&lt;T, Exclude&lt;keyof T, K&gt;&gt; &amp; Record&lt;K, N&gt;;
  
  function mapProperties&lt;
          WRAPPER_TYPE,
          WRAPPER_KEYS extends (keyof WRAPPER_TYPE &amp; PropertyNamesByType&lt;WRAPPER_TYPE, OLD_TYPE&gt;),
          OLD_TYPE,
          NEW_TYPE
      &gt;(obj: WRAPPER_TYPE,
        mapper: (value: OLD_TYPE) =&gt; NEW_TYPE,
        ...properties: WRAPPER_KEYS[]
      ): OverwriteType&lt;WRAPPER_TYPE, WRAPPER_KEYS, NEW_TYPE&gt; {
  
      const result: OverwriteType&lt;WRAPPER_TYPE, WRAPPER_KEYS, NEW_TYPE&gt; = &lt;any&gt;obj;
      properties.forEach(key =&gt; {
          (&lt;any&gt;result[key]) = mapper(&lt;any&gt;obj[key]);
      });
      return result;
  }
  </code></pre>
  
  <p>This actually seems to work, but there are two oddities:</p>
  
  <ol>
  <li>The line <code>WRAPPER_KEYS extends (keyof WRAPPER_TYPE &amp; PropertyNamesByType&lt;WRAPPER_TYPE, OLD_TYPE&gt;)</code>. I think it should work with just <code>WRAPPER_KEYS extends PropertyNamesByType&lt;WRAPPER_TYPE, OLD_TYPE&gt;</code>, without the <code>&amp; keyof WRAPPER_TYPE</code>, because the later should actually not add any additional information (I discovered this quite by accident). However if I omit this, TypeScript will behave as if ALL string properties were converted. What magic is happening there?</li>
  <li>In the line <code>(&lt;any&gt;result[key]) = mapper(&lt;any&gt;obj[key]);</code> I need those two <code>&lt;any&gt;</code>-casts. Is there any way to get rid of those?</li>
  </ol>