I'm not entirely certain about your intentions, so I can't determine the most suitable solution for your situation:
One approach involves utilizing conditional types to derive K
from types T
and your desired R
. However, this method may introduce more complexities than it resolves:
type ValueOf<T> = T[keyof T];
type KeyofMatching<T, R extends ValueOf<T>> =
ValueOf<{ [K in keyof T]: T[K] extends R ? K : never }>;
The KeyofMatching
type alias takes a type T
and one of its property value types R
, returning all the keys K
associated with that type. Hence, T[KeyofMatching<T, R>]
always equals R
. Unfortunately, TypeScript may require an assertion when converting values between these types. A function can facilitate this conversion:
function asR<T, R extends ValueOf<T>>(x: T[KeyofMatching<T, R>]): R {
return x as any;
}
By defining a class as shown below, you can extract data based on the given parameters:
class Extractor<T, R extends T[keyof T]> {
constructor(private item: T, private key: KeyofMatching<T, R>) { }
extract(): R {
return asR(this.item[this.key]); // note the asR()
}
}
While functional, creating a new instance of Extractor
may result in wider inferred types for R
:
const extractorNotSoGood = new Extractor({a: "you", b: true}, "b");
// inferred as Extractor<{a: string, b: boolean}, string | boolean>;
To ensure narrower type inference for R
, explicit specification might be necessary:
const e = new Extractor<{a: string, b: boolean}, boolean>({a: "you", b: true}, "b");
An alternative approach involves using a static builder method instead of the constructor, along with storing a function instead of just a key:
class Extractor<T, R extends T[keyof T]> {
constructor(private item: T, private extractorFunction: (x: T) => R) { }
extract(): R {
return this.extractorFunction(this.item);
}
static make<T, K extends keyof T>(item: T, key: K): Extractor<T, T[K]> {
return new Extractor(item, i => i[key]);
}
}
Utilizing the make
method provides better behavior and inference capabilities:
const e = Extractor.make({ a: "you", b: true }, "b");
// e is an Extractor<{a: string, b: boolean}, boolean>
This method presents its own challenges but could offer a viable solution. Hopefully, one of these approaches helps you move forward. Best of luck!