Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript: Type-mapped type cannot be indexed with original type

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.

like image 837
Stan Strum Avatar asked Dec 06 '25 08:12

Stan Strum


1 Answers

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;
    }
}
like image 67
Rich N Avatar answered Dec 07 '25 23:12

Rich N