I'm trying to use Conditional Mapped types to get only allow keys of an object that are of a particular type as a parameter in a function.
However, I'm running into an issue in that the correct type is not being inferred when I do so.
I've created an example to demonstrate (view on typescript playground):
interface TraversableType{
name: string;
}
interface TypeOne extends TraversableType{
typeNotTraversable: string;
typeTwo: TypeTwo;
typeThree: TypeThree;
}
interface TypeTwo extends TraversableType{
typeTwoNotTraversable: string;
typeOne: TypeOne;
typeThree: TypeThree;
}
interface TypeThree extends TraversableType{
typeThreeProp: string;
}
type TraversablePropNames<T> = { [K in keyof T]: T[K] extends TraversableType ? K : never }[keyof T];
//given start object, return
function indexAny<T extends TraversableType, K extends keyof T>(startObj: T, key: K): T[K] {
return startObj[key];
}
//same thing, but with only "traversable" keys allow
function indexTraverseOnly<T extends TraversableType, K extends TraversablePropNames<T>>(startObj: T, key: K): T[K] {
return startObj[key];
}
let t2: TypeTwo;
type keyType = keyof TypeTwo; // "typeTwoNotTraversable" | "typeOne" | "typeThree" | "name"
type keyType2 = TraversablePropNames<TypeTwo>; // "typeOne" | "typeThree"
let r1 = indexAny(t2, 'typeOne'); // TypeOne
let r2 = indexTraverseOnly(t2, 'typeOne'); // TypeOne | TypeThree
Notice how when using K extends keyof T the indexAny function is able to infer the correct return type.
However, when I try to use the TraversablePropNames conditional mapped type to defined the key, it doesn't know if it's TypeOne or TypeTwo.
Is there some way to write the function so that it will ONLY allow keys of TraversableType AND will infer the type correctly?
UPDATE:
Interestingly... it seems to work 1 property deep IF I wrap the method in a generic class and pass the instance in (instead of as the first param). However, it only seem to work for one traversal... then it fails again:
class xyz<T>{
private traversable: T;
constructor(traversable: T) {
this.traversable = traversable;
}
indexTraverseOnly<K extends TraversablePropNames<T>>(key: K): T[K] {
return this.traversable[key];
}
indexTraverseTwice<K extends TraversablePropNames<T>, K2 extends TraversablePropNames<T[K]>>(key: K, key2: K2): T[K][K2] {
return this.traversable[key][key2];
}
}
let t2: TypeTwo;
let r3Obj = new xyz(t2);
let r3 = r3Obj.indexTraverseOnly('typeOne'); // TypeOne (WORKS!)
let r4 = r3Obj.indexTraverseTwice('typeOne', 'typeThree'); // TypeTwo | TypeThree