<p dir="auto">When mapping a object, TS can't retain the keys accessor.</p>
<p dir="auto">Following example uses <a href="https://lodash.com/docs#mapValues" rel="nofollow">lodash _.mapValues</a>, which maps a object.</p>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="var newUsers = _.mapValues({
  'fred':    { 'user': 'fred',    'age': 40 },
  'pebbles': { 'user': 'pebbles', 'age': 1 }
}, function(o) { return o.age; }); // -&gt; _.Dictionary&lt;number&gt;
let ageFred = newUsers['fred']; // is number
newUsers.fred // &lt;-- invalid because TypeScript can't retain the keys."><pre class="notranslate"><span class="pl-k">var</span> <span class="pl-s1">newUsers</span> <span class="pl-c1">=</span> <span class="pl-s1">_</span><span class="pl-kos">.</span><span class="pl-en">mapValues</span><span class="pl-kos">(</span><span class="pl-kos">{</span>
  <span class="pl-s">'fred'</span>:    <span class="pl-kos">{</span> <span class="pl-s">'user'</span>: <span class="pl-s">'fred'</span><span class="pl-kos">,</span>    <span class="pl-s">'age'</span>: <span class="pl-c1">40</span> <span class="pl-kos">}</span><span class="pl-kos">,</span>
  <span class="pl-s">'pebbles'</span>: <span class="pl-kos">{</span> <span class="pl-s">'user'</span>: <span class="pl-s">'pebbles'</span><span class="pl-kos">,</span> <span class="pl-s">'age'</span>: <span class="pl-c1">1</span> <span class="pl-kos">}</span>
<span class="pl-kos">}</span><span class="pl-kos">,</span> <span class="pl-k">function</span><span class="pl-kos">(</span><span class="pl-s1">o</span><span class="pl-kos">)</span> <span class="pl-kos">{</span> <span class="pl-k">return</span> <span class="pl-s1">o</span><span class="pl-kos">.</span><span class="pl-c1">age</span><span class="pl-kos">;</span> <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span> <span class="pl-c">// -&gt; _.Dictionary&lt;number&gt;</span>
<span class="pl-k">let</span> <span class="pl-s1">ageFred</span> <span class="pl-c1">=</span> <span class="pl-s1">newUsers</span><span class="pl-kos">[</span><span class="pl-s">'fred'</span><span class="pl-kos">]</span><span class="pl-kos">;</span> <span class="pl-c">// is number</span>
<span class="pl-s1">newUsers</span><span class="pl-kos">.</span><span class="pl-c1">fred</span> <span class="pl-c">// &lt;-- invalid because TypeScript can't retain the keys.</span></pre></div>
<p dir="auto">I can't think a way to type _.mapValues so it would retain the keys. Retaining keys requires new kind of typing capabilities for the TypeScript.</p>
<p dir="auto">I find myself needing to retain keys in surprising many places, e.g. when uncurrying a object full of functions.</p>
<h2 dir="auto">Motivations</h2>
<p dir="auto">A lot of JavaScript library/framework/pattern involve computation based on the property name of an object. For example <a href="http://backbonejs.org/" rel="nofollow">Backbone</a> model, functional transformation <code class="notranslate">pluck</code>, <a href="https://github.com/facebook/immutable-js">ImmutableJS</a> are all based on such mechanism.</p>
<div class="highlight highlight-source-js notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="//backbone
var Contact = Backbone.Model.extend({})
var contact = new Contact();
contact.get('name');
contact.set('age', 21);

// ImmutableJS
var map = Immutable.Map({ name: 'François', age: 20 });
map = map.set('age', 21);
map.get('age'); // 21

//pluck
var arr = [{ name: 'François' }, { name: 'Fabien' }];
_.pluck(arr, 'name') // ['François', 'Fabien'];"><pre class="notranslate"><span class="pl-c">//backbone</span>
<span class="pl-k">var</span> <span class="pl-v">Contact</span> <span class="pl-c1">=</span> <span class="pl-v">Backbone</span><span class="pl-kos">.</span><span class="pl-c1">Model</span><span class="pl-kos">.</span><span class="pl-en">extend</span><span class="pl-kos">(</span><span class="pl-kos">{</span><span class="pl-kos">}</span><span class="pl-kos">)</span>
<span class="pl-k">var</span> <span class="pl-s1">contact</span> <span class="pl-c1">=</span> <span class="pl-k">new</span> <span class="pl-v">Contact</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-s1">contact</span><span class="pl-kos">.</span><span class="pl-en">get</span><span class="pl-kos">(</span><span class="pl-s">'name'</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-s1">contact</span><span class="pl-kos">.</span><span class="pl-en">set</span><span class="pl-kos">(</span><span class="pl-s">'age'</span><span class="pl-kos">,</span> <span class="pl-c1">21</span><span class="pl-kos">)</span><span class="pl-kos">;</span>

<span class="pl-c">// ImmutableJS</span>
<span class="pl-k">var</span> <span class="pl-s1">map</span> <span class="pl-c1">=</span> <span class="pl-v">Immutable</span><span class="pl-kos">.</span><span class="pl-en">Map</span><span class="pl-kos">(</span><span class="pl-kos">{</span> <span class="pl-c1">name</span>: <span class="pl-s">'François'</span><span class="pl-kos">,</span> <span class="pl-c1">age</span>: <span class="pl-c1">20</span> <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-s1">map</span> <span class="pl-c1">=</span> <span class="pl-s1">map</span><span class="pl-kos">.</span><span class="pl-en">set</span><span class="pl-kos">(</span><span class="pl-s">'age'</span><span class="pl-kos">,</span> <span class="pl-c1">21</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-s1">map</span><span class="pl-kos">.</span><span class="pl-en">get</span><span class="pl-kos">(</span><span class="pl-s">'age'</span><span class="pl-kos">)</span><span class="pl-kos">;</span> <span class="pl-c">// 21</span>

<span class="pl-c">//pluck</span>
<span class="pl-k">var</span> <span class="pl-s1">arr</span> <span class="pl-c1">=</span> <span class="pl-kos">[</span><span class="pl-kos">{</span> <span class="pl-c1">name</span>: <span class="pl-s">'François'</span> <span class="pl-kos">}</span><span class="pl-kos">,</span> <span class="pl-kos">{</span> <span class="pl-c1">name</span>: <span class="pl-s">'Fabien'</span> <span class="pl-kos">}</span><span class="pl-kos">]</span><span class="pl-kos">;</span>
<span class="pl-s1">_</span><span class="pl-kos">.</span><span class="pl-en">pluck</span><span class="pl-kos">(</span><span class="pl-s1">arr</span><span class="pl-kos">,</span> <span class="pl-s">'name'</span><span class="pl-kos">)</span> <span class="pl-c">// ['François', 'Fabien'];</span></pre></div>
<p dir="auto">We can easily understand in those examples the relation between the api and the underlying type constraint.<br>
In the case of the backbone model, it is just a kind of <em>proxy</em> for an object of type :</p>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="interface Contact {
  name: string;
  age: number;
}"><pre class="notranslate"><span class="pl-k">interface</span> <span class="pl-smi">Contact</span> <span class="pl-kos">{</span>
  <span class="pl-c1">name</span>: <span class="pl-smi">string</span><span class="pl-kos">;</span>
  <span class="pl-c1">age</span>: <span class="pl-smi">number</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span></pre></div>
<p dir="auto">For the case of <code class="notranslate">pluck</code>, it's a transformation</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="T[] =&gt; U[]"><pre class="notranslate"><code class="notranslate">T[] =&gt; U[]
</code></pre></div>
<p dir="auto">where U is the type of a property of T <code class="notranslate">prop</code>.</p>
<p dir="auto">However we have no way to express such relation in TypeScript, and ends up with dynamic type.</p>
<h2 dir="auto">Proposed solution</h2>
<p dir="auto">The proposed solution is to introduce a new syntax for type <code class="notranslate">T[prop]</code> where <code class="notranslate">prop</code> is an argument of the function using such type as return value or type parameter.<br>
With this new type syntax we could write the following definition :</p>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="declare module Backbone {

  class Model&lt;T&gt; {
    get(prop: string): T[prop];
    set(prop: string, value: T[prop]): void;
  }
}

declare module ImmutableJS {
  class Map&lt;T&gt; {
    get(prop: string): T[prop];
    set(prop: string, value: T[prop]): Map&lt;T&gt;;
  }
}

declare function pluck&lt;T&gt;(arr: T[], prop: string): Array&lt;T[prop]&gt;  // or T[prop][] "><pre class="notranslate"><span class="pl-k">declare</span> module <span class="pl-smi">Backbone</span> <span class="pl-kos">{</span>

  <span class="pl-k">class</span> <span class="pl-smi">Model</span><span class="pl-c1">&lt;</span><span class="pl-smi">T</span><span class="pl-c1">&gt;</span> <span class="pl-kos">{</span>
    <span class="pl-c1">get</span><span class="pl-kos">(</span><span class="pl-s1">prop</span>: <span class="pl-smi">string</span><span class="pl-kos">)</span>: <span class="pl-smi">T</span><span class="pl-kos">[</span><span class="pl-smi">prop</span><span class="pl-kos">]</span><span class="pl-kos">;</span>
    <span class="pl-c1">set</span><span class="pl-kos">(</span><span class="pl-s1">prop</span>: <span class="pl-smi">string</span><span class="pl-kos">,</span> <span class="pl-s1">value</span>: <span class="pl-smi">T</span><span class="pl-kos">[</span><span class="pl-smi">prop</span><span class="pl-kos">]</span><span class="pl-kos">)</span>: <span class="pl-smi"><span class="pl-k">void</span></span><span class="pl-kos">;</span>
  <span class="pl-kos">}</span>
<span class="pl-kos">}</span>

<span class="pl-k">declare</span> module <span class="pl-smi">ImmutableJS</span> <span class="pl-kos">{</span>
  <span class="pl-k">class</span> <span class="pl-smi">Map</span><span class="pl-c1">&lt;</span><span class="pl-smi">T</span><span class="pl-c1">&gt;</span> <span class="pl-kos">{</span>
    <span class="pl-c1">get</span><span class="pl-kos">(</span><span class="pl-s1">prop</span>: <span class="pl-smi">string</span><span class="pl-kos">)</span>: <span class="pl-smi">T</span><span class="pl-kos">[</span><span class="pl-smi">prop</span><span class="pl-kos">]</span><span class="pl-kos">;</span>
    <span class="pl-c1">set</span><span class="pl-kos">(</span><span class="pl-s1">prop</span>: <span class="pl-smi">string</span><span class="pl-kos">,</span> <span class="pl-s1">value</span>: <span class="pl-smi">T</span><span class="pl-kos">[</span><span class="pl-smi">prop</span><span class="pl-kos">]</span><span class="pl-kos">)</span>: <span class="pl-smi">Map</span><span class="pl-kos">&lt;</span><span class="pl-smi">T</span><span class="pl-kos">&gt;</span><span class="pl-kos">;</span>
  <span class="pl-kos">}</span>
<span class="pl-kos">}</span>

<span class="pl-k">declare</span> <span class="pl-k">function</span> <span class="pl-s1">pluck</span><span class="pl-c1">&lt;</span><span class="pl-smi">T</span><span class="pl-c1">&gt;</span><span class="pl-kos">(</span><span class="pl-s1">arr</span>: <span class="pl-smi">T</span><span class="pl-kos">[</span><span class="pl-kos">]</span><span class="pl-kos">,</span> <span class="pl-s1">prop</span>: <span class="pl-smi">string</span><span class="pl-kos">)</span>: <span class="pl-smi">Array</span><span class="pl-kos">&lt;</span><span class="pl-smi">T</span><span class="pl-kos">[</span><span class="pl-smi">prop</span><span class="pl-kos">]</span><span class="pl-kos">&gt;</span>  <span class="pl-c">// or T[prop][] </span></pre></div>
<p dir="auto">This way, when we use our Backbone model, TypeScript could correctly type-check the <code class="notranslate">get</code> and <code class="notranslate">set</code> call.</p>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="interface Contact {
  name: string;
  age: number;
}
var contact: Backbone.Model&lt;Contact&gt;;

var age = contact.get('age');
contact.set('name', 3) /// error"><pre class="notranslate"><span class="pl-k">interface</span> <span class="pl-smi">Contact</span> <span class="pl-kos">{</span>
  <span class="pl-c1">name</span>: <span class="pl-smi">string</span><span class="pl-kos">;</span>
  <span class="pl-c1">age</span>: <span class="pl-smi">number</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span>
<span class="pl-k">var</span> <span class="pl-s1">contact</span>: <span class="pl-smi">Backbone</span><span class="pl-kos">.</span><span class="pl-smi">Model</span><span class="pl-kos">&lt;</span><span class="pl-smi">Contact</span><span class="pl-kos">&gt;</span><span class="pl-kos">;</span>

<span class="pl-k">var</span> <span class="pl-s1">age</span> <span class="pl-c1">=</span> <span class="pl-s1">contact</span><span class="pl-kos">.</span><span class="pl-en">get</span><span class="pl-kos">(</span><span class="pl-s">'age'</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-s1">contact</span><span class="pl-kos">.</span><span class="pl-en">set</span><span class="pl-kos">(</span><span class="pl-s">'name'</span><span class="pl-kos">,</span> <span class="pl-c1">3</span><span class="pl-kos">)</span> <span class="pl-c">/// error</span></pre></div>
<h2 dir="auto">The <code class="notranslate">prop</code> constant</h2>
<h3 dir="auto">Constraint</h3>
<p dir="auto">Obviously the constant must be of a type that can be used as  index type (<code class="notranslate">string</code>, <code class="notranslate">number</code>, <code class="notranslate">Symbol</code>).</p>
<h3 dir="auto">Case of indexable</h3>
<p dir="auto">Let's give a look at our <code class="notranslate">Map</code> definition:</p>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="declare module ImmutableJS {
  class Map&lt;T&gt; {
    get(prop: string): T[string];
    set(prop: string, value: T[string]): Map&lt;T&gt;;
  }
}"><pre class="notranslate"><span class="pl-k">declare</span> module <span class="pl-smi">ImmutableJS</span> <span class="pl-kos">{</span>
  <span class="pl-k">class</span> <span class="pl-smi">Map</span><span class="pl-c1">&lt;</span><span class="pl-smi">T</span><span class="pl-c1">&gt;</span> <span class="pl-kos">{</span>
    <span class="pl-c1">get</span><span class="pl-kos">(</span><span class="pl-s1">prop</span>: <span class="pl-smi">string</span><span class="pl-kos">)</span>: <span class="pl-smi">T</span><span class="pl-kos">[</span><span class="pl-smi">string</span><span class="pl-kos">]</span><span class="pl-kos">;</span>
    <span class="pl-c1">set</span><span class="pl-kos">(</span><span class="pl-s1">prop</span>: <span class="pl-smi">string</span><span class="pl-kos">,</span> <span class="pl-s1">value</span>: <span class="pl-smi">T</span><span class="pl-kos">[</span><span class="pl-smi">string</span><span class="pl-kos">]</span><span class="pl-kos">)</span>: <span class="pl-smi">Map</span><span class="pl-kos">&lt;</span><span class="pl-smi">T</span><span class="pl-kos">&gt;</span><span class="pl-kos">;</span>
  <span class="pl-kos">}</span>
<span class="pl-kos">}</span></pre></div>
<p dir="auto">If <code class="notranslate">T</code> is indexable, our map inherit of this behavior:</p>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="var map = new ImmutableJS.Map&lt;{ [index: string]: number}&gt;;"><pre class="notranslate"><span class="pl-k">var</span> <span class="pl-s1">map</span> <span class="pl-c1">=</span> <span class="pl-k">new</span> <span class="pl-smi">ImmutableJS</span><span class="pl-kos">.</span><span class="pl-c1">Map</span><span class="pl-kos">&lt;</span><span class="pl-kos">{</span> <span class="pl-kos">[</span><span class="pl-s1">index</span>: <span class="pl-smi">string</span><span class="pl-kos">]</span>: <span class="pl-smi">number</span><span class="pl-kos">}</span><span class="pl-kos">&gt;</span><span class="pl-kos">;</span></pre></div>
<p dir="auto">Now <code class="notranslate">get</code> has for type <code class="notranslate">get(prop: string): number</code>.</p>
<h2 dir="auto">Interrogation</h2>
<p dir="auto">Now There is some cases where I have pain to think of a <em>correct</em> behavior, let's start again with our <code class="notranslate">Map</code> definition.<br>
If instead of passing <code class="notranslate">{ [index: string]: number }</code> as type parameter we would have given<br>
<code class="notranslate">{ [index: number]: number }</code> should the compiler raise an error ?</p>
<p dir="auto">if we use <code class="notranslate">pluck</code> with a dynamic expression for prop instead of a constant :</p>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="var contactArray: Contact[] = []
function pluckContactArray(prop: string) {
  return _.pluck(myArray, prop);
}"><pre class="notranslate"><span class="pl-k">var</span> <span class="pl-s1">contactArray</span>: <span class="pl-smi">Contact</span><span class="pl-kos">[</span><span class="pl-kos">]</span> <span class="pl-c1">=</span> <span class="pl-kos">[</span><span class="pl-kos">]</span>
<span class="pl-k">function</span> <span class="pl-en">pluckContactArray</span><span class="pl-kos">(</span><span class="pl-s1">prop</span>: <span class="pl-smi">string</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
  <span class="pl-k">return</span> <span class="pl-s1">_</span><span class="pl-kos">.</span><span class="pl-en">pluck</span><span class="pl-kos">(</span><span class="pl-s1">myArray</span><span class="pl-kos">,</span> <span class="pl-s1">prop</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span></pre></div>
<p dir="auto">or with a constant that is not a property of the type passed as parameter.<br>
should the call to <code class="notranslate">pluck</code> raise an error since the compiler cannot infer the type <code class="notranslate">T[prop]</code>, shoud <code class="notranslate">T[prop]</code> be  resolved to <code class="notranslate">{}</code> or <code class="notranslate">any</code>, if so should the compiler with <code class="notranslate">--noImplicitAny</code> raise an error ?</p>