Home > other >  Why can't typescript infer the type of `T[key of T]` for type parameter `T` here?
Why can't typescript infer the type of `T[key of T]` for type parameter `T` here?

Time:06-10

I'm trying to write a generic class that is passed a key key corresponding to a key of one of a set of known interfaces on construction and that can later be passed an object thing and type-safely access thing[key]. This is what I've got:

interface AB {
  a: string
  b: number
}

interface BC {
  b: number
  c: Date
}

type ABC = AB | BC

class Getter<T extends ABC> {
  key: keyof T;

  constructor(key: keyof T) {
    this.key = key;
  }

  get(thing: T): string {
    const value = thing[this.key];
    return value.toString();
  }
  
}

const getter = new Getter<AB>('b');

Playground Link

Here, I'd expect Typescript to infer that because T extends ABC that T[keyof T] = AB[keyof AB] | BC [keyof BC] = string | number | date. However, it seems to get stuck at T[keyof T]. Even adding an as AB[keyof AB] | BC[keyof BC] to that line doesn't fix it, I need as unknown as AB[keyof AB] | BC[keyof BC]! Is there any way to get this working without that line?

Also, is there any way I could parameterize over the value of key as a type instead of parameterizing over the type of thing?

CodePudding user response:

This is simply a limitation of the nature of typescript. You need to check members, then do a typed cast at runtime. Hope that helps!

For example:

if (AnimalObject.Object.Keys().Includes('Name')) {
  // We now know the generic type has a matching member
}

As for an implementation, I was would make a list of interfaces that 'T' can be then make an array of members for each interface, following the above logic you may end up with something like:

// Store your valid types for checking
const ValidInterfacesSet: any[] = {Dog,Cat,car,Mammal};

//Look through members and confirm them... 
GetAndCastType(typeToCheck: any) {
    typeToCheckKeys: string = typeToCheck.Object.keys();
    for (inter: any of ValidInterfacesSet) {
        const interMemberNames: string[] = inter.object.keys();
        
        if(array1.sort().join(',')=== array2.sort().join(',')) {
          // Returning the casted type if the iter members contain the same as typeToCheckMembers
             return typeToCheck as inter; 
        }
    }
}

CodePudding user response:

Jamie Nicholl-Shelley's answer is definitely wrong. His answer is not even Typescript.

How about this solution? Playground

It has little overhead because it makes warpper function makeGetter. But you can infer type safely. I didn't use class, but I think you can change 14~16 line to class.

CodePudding user response:

When T extends ABC, it can have many other properties and types. Which can therefore not have .toString() method.

type G = {
  a: string;
  b: number;
  hello: undefined;
};

const g = new Getter<G, keyof G>("hello");

g.get({
  a: "a",
  b: 1,
  hello: undefined
}); // undefined does not have .toString() method

Playground Link

  • Related