title: Narrow indexed type by passing keys and associated values types to generic
body: <h2>Differences from similar question</h2>
  <p>Unfortunately, the great answers to question <a href="https://stackoverflow.com/q/65303532/4818123">Pass implicit keys and values type relationship to TypeScript generic</a> does not cover the problem in current question: in <code>generateInputsAccessObject</code>, the target function of that question, we don't not use the subtype-dependent properties.</p>
  <p>In fact, the above question has been solved without passing the implicit keys and values type relationship to TypeScript generic, which is the heading, but I suppose now we can't avoid it anymore.</p>
  <h2>Target</h2>
  <p>Create the <code>EntitySpecification</code>, the generic type of <code>ProductSpecification</code> such as:</p>
  <ol>
  <li><code>properties</code>'s type must be <strong>non-indexed</strong>. It mean that TypeScript compiler must know what <code>ID</code> and <code>price</code> are exists, but other keys - does not exist.</li>
  <li><code>properties</code> must be iteratable (<code>Object.entires(&quot;ProductSpecification.properties&quot;)</code> must work).</li>
  <li><code>ID</code> and <code>price</code> could have different type, but when we call <code>EntitySpecification.properties.ID</code> or <code>EntitySpecification.properties.price</code>, TypeScript compiler must know which type.</li>
  </ol>
  <pre class="lang-js prettyprint-override"><code>
  export type Product = {
    ID: string;
    price: number;
  };
  
  const ProductSpecification: EntitySpecification&lt;keyof Product&gt; = {
    name: &quot;Product&quot;,
    properties: {
      ID: {
        type: DataTypes.string,
        emptyStringIsAllowed: false
      },
      price: {
        type: DataTypes.number,
        numberSet: NumbersSets.nonNegativeInteger
      }
    }
  };
  </code></pre>
  <p>Here:</p>
  <pre><code>export enum DataTypes {
    number = &quot;NUMBER&quot;,
    string = &quot;STRING&quot;
  }
  
  
  export enum NumbersSets {
    naturalNumber = &quot;NATURAL_NUMBER&quot;,
    nonNegativeInteger = &quot;NON_NEGATIVE_INTEGER&quot;,
    negativeInteger = &quot;NEGATIVE_INTEGER&quot;,
    negativeIntegerOrZero = &quot;NEGATIVE_INTEGER_OR_ZERO&quot;,
    anyInteger = &quot;ANY_INTEGER&quot;,
    positiveDecimalFraction = &quot;POSITIVE_DECIMAL_FRACTION&quot;,
    negativeDecimalFraction = &quot;NEGATIVE_DECIMAL_FRACTION&quot;,
    decimalFractionOfAnySign = &quot;DECIMAL_FRACTION_OF_ANY_SIGN&quot;,
    anyRealNumber = &quot;ANY_REAL_NUMBER&quot;
  }
  
  export type StringSpecification = {
    readonly type: DataTypes.string;
    readonly emptyStringIsAllowed: boolean;
  };
  
  export type NumberSpecification = {
    readonly type: DataTypes.number;
    readonly numberSet: NumbersSets;
  };
  </code></pre>
  <p>Off course, the <code>EntitySpecification</code> does no know at advance the keys, however the keys count could be arbitrary large (not 2 as in current <code>ProductSpecification</code>).</p>
  <h2>Best of me for now</h2>
  <p>Below solution satisfies to first two targets:</p>
  <pre><code>export type EntitySpecification&lt;Keys extends string&gt; = {
    readonly name: string;
    readonly properties: { [key in Keys]: StringSpecification | NumberSpecification };
  };
  </code></pre>
  <p>Here the conflict situation, the consequence of violation of third condition:</p>
  <pre class="lang-js prettyprint-override"><code>type StringValidationRules = {
    emptyStringIsAllowed: boolean;
  };
  
  const ID_ValidationRules: StringValidationRules = {
    emptyStringIsAllowed: ProductSpecification__EXPERIMENTAL.properties.ID.emptyStringIsAllowed;
  }
  </code></pre>
  <p>Because TypeScript does not know that <code>ID</code> has <code>StringSpecification</code> type, we have below error:</p>
  <pre><code>TS2339: Property 'emptyStringIsAllowed' does not exist on type 'StringSpecification | NumberSpecification'.   Property 'emptyStringIsAllowed' does not exist on type 'NumberSpecification'.
  </code></pre>