Home > database >  Attaining value type of nested record
Attaining value type of nested record

Time:11-29

I would like to create a function that can manipulate values of a nested Record, based off the collection name. In my example code, the value type is not being attained:

type Player = { name:string }
type Point = { x:number, y:number }


const collections = {
    players: {} as Record<string, Player>,
    positions: {} as Record<string, Point>,
}

type Collections = typeof collections;


function handleEntity<
    Key extends keyof Collections,
    Col extends Collections[Key],
    Val extends Col[keyof Col],
>(name:Key, value:Val){
    switch(name){
        case 'positions': value.x   // <-- Not valid :(
    }
}

Why is Typescript not able to infer Val? Is there a workaround?

CodePudding user response:

The TypeScript compiler generally has a hard time properly narrowing down the types of generically typed variables. The relation between name and value is understood by the compiler when you are calling the function. But narrowing the type of value by checking name in the implementation is currently not possible, at least when name and value are generic types.

We can remove the generic types and construct a union type for the parameters.

function handleEntity(...[name, value]: { 
    [K in keyof Collections]: [K, Collections[K][keyof Collections[K]]] 
  }[keyof Collections]
){
    switch(name){
        case 'positions': value.x   // properly narrowed :)
    }
}

handleEntity("players", { name: "abc" })
handleEntity("positions", { x: 1, y: 0 })

Since the name, value tuple acts as a discriminated union, the compiler can properly narrow the type of value by checking the value of name.

If you still need handleEntity to be generic, you can add the generic signature as an overload.

function handleEntity<
    Key extends keyof Collections,
    Col extends Collections[Key],
    Val extends Col[keyof Col],
>(name:Key, value:Val): void
function handleEntity(...[name, value]: { 
    [K in keyof Collections]: [K, Collections[K][keyof Collections[K]]] 
  }[keyof Collections]
){
    switch(name){
        case 'positions': value.x   // properly narrowed :)
    }
}

Playground

  • Related