Search code examples
typescripttypescript-typings

how to infer property and value type from object types


I have an object type; I want to get a new tuple type like: [property, value], when I input the property, them typescript can infer the type of value automatically, but I can't solve the problem.

Example:

type DoubleTuple<T extends Record<any, any>> = T extends Record<infer P, any>
  ? [P, T[P]]
  : never;

interface ExampleModel {
  a: number;
  b: string;
}
// when i input the property 'a', number | string was inferred , but number expected
const tuple: DoubleTuple<ExampleModel> = ["a", "3"];

Solution

  • You want DoubleTuple<T> type to be a union of entry tuple types for each key-value pair in T. So DoubleTuple<{a: A, b: B, c: C}> should be ["a", A] | ["b", B] | ["c", C]. But your version doesn't do that at all.

    First, T extends Record<infer P, any> ? P : never is equivalent to just keyof T (unless T is a union, but that doesn't seem to be your use case), so you can collapse your type to [keyof T, T[keyof T]], which mixes all the keys and properties together. Really you want to distribute your operation over the elements of keyof T, so that it looks like [P, T[P]] for each P in keyof T.

    There are different ways to accomplish this but a common one is to build a distributive object type as coined in microsoft/TypeScript#47109. It's of the form {[P in K]: F<P>}[K] where K is some union of keylike types (such as keyof T). This produces a mapped type into which you immediately index to get the union of F<P> for each P in K.

    That gives us the following:

    type DoubleTuple<T extends Record<any, any>> = 
       { [P in keyof T]: [P, T[P]] }[keyof T];
    

    Let's test it out:

    interface ExampleModel {
      a: number;
      b: string;
    }
       
    type DTEM = DoubleTuple<ExampleModel>;
    // type DTEM = ["a", number] | ["b", string]
    
    let tuple: DoubleTuple<ExampleModel>;
    tuple = ["a", 2]; // okay
    tuple = ["a", "x"]; // error
    tuple = ["b", "x"]; // okay
    tuple = ["b", 2]; // error
    

    Looks good.

    Playground link to code