I am writing a class that is instantiated that has a method whose return type is an object with the keys from the generic type inferred by the compiler. I am running into issues with the following code snippet as the compiler tells me that the generic type U
cannot be indexed by type T
-- which I believe is incorrect due to the fact that U
is type-mapped using T
, therefore T will always be able to index an object of type U
. Is there a way of affirming to the compiler that this is the case?
Minimal snippet:
class DynamicReturnFormat<T extends string, U = { [ key in T ]: string }> {
constructor(private keys: T[]) {}
public returnObject(): U {
const obj = {} as U;
for (const key of this.keys) {
obj[key] = "test";
// error TS2536: Type 'T' cannot be used to index type 'U'.
}
return obj;
}
}
As per Rich N.'s answer, this class will be used as follows:
declare const input: Buffer;
const fmt = ["length", "packetID", ...];
const handshakeParser = new DataParser(fmt);
const decodedObject = handshakeParser.decode(input);
For context: DynamicReturnFormat
is DataParser
and returnObject
is decode
. I would like decodedObject
to have a non-"one size fits all" type that only includes the properties that were passed into the class instantiation.
It's a little unclear how you want this to be used, but I'm guessing you want to do something like:
const keys = ["a", "b", "c"];
const drf = new DynamicReturnFormat(keys);
const result = drf.returnObject();
Then result should be {a: "test", b: "test", c: "test" }?
If so, the code below works. I've had to take out the second generic variable U, and use a Partial as {} is not assignable to type {[key in T]: string}:
class DynamicReturnFormat<T extends string> {
constructor(private keys: T[]) { }
public returnObject(): { [key in T]: string } {
const obj: Partial<{ [key in T]: string }>= {};
for (const key of this.keys) {
obj[key] = "test";
}
return obj as { [key in T]: string };
}
}
The code above works, but the generic T doesn't add much value I think. If you're hoping to somehow constrain the array passed into the constructor I don't think that can be done this way (?). So the code below may be better:
class DynamicReturnFormat {
constructor(private keys: string[]) { }
public returnObject(): { [key: string]: string } {
const obj: { [key: string]: string } = {};
for (const key of this.keys) {
obj[key] = "test";
}
return obj;
}
}