简体   繁体   中英

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

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] AB[keyof AB] | BC [keyof BC] = string | number | date string | number | date string | number | date . However, it seems to get stuck at T[keyof T] . Even adding an as AB[keyof AB] | BC[keyof BC] 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] 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 ?

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

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

Yes, nothing easier than that:

class Getter<Key extends string | symbol> {
  key: Key;

  constructor(key: Key) {
    this.key = key;
  }

  get(thing: {[k in Key]: string | number | Date}): string {
    const value = thing[this.key];
    return value.toString();
  }
}

( Playground demo )

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; 
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM