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');
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