0

I cannot even find proper words to formulate the question. Here is a simplified code of what I want to achieve.

class Test<T, TId extends keyof T> {
  public create(id: T[TId]): T {
    return {
      [TId]: id, // Error here. I want to set property `TId` to value `id`,
    } as T;
  }
}

interface A {
  id: number;
}

interface B {
  fileName: string;
}

new Test<A, 'id'>().create(123); // Want to get { id: 123 }
new Test<B, 'fileName'>().create('file'); // Want to get { fileName: 'file' }

Error is: Conversion of type '{ [x: number]: T[TId]; }' to type 'T' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. 'T' could be instantiated with an arbitrary type which could be unrelated to '{ [x: number]: T[TId]; }'.ts(2352)

AKd
  • 501
  • 4
  • 17

1 Answers1

0

What you're trying to do isn't possible purely in types I'm afraid. The problem is here:

new Test<A, 'id'>().create(123); // Want to get { id: 123 }

That compiles to this JavaScript:

new Test().create(123);

You can see it's impossible to return an object with the right key here, because your type parameter ('id') doesn't exist in the compiled code. Type information is not present at runtime.

To fix this, you need to change that design, and pass 'id' as a string parameter to either create or Test(). For example:

class Test<T extends object, TId extends keyof T = keyof T> {

  constructor(private readonly key: TId) { }

  public create(value: T[TId]): T {
    return { [this.key]: value } as T;
  }
}

interface A {
  id: number;
}

interface B {
  fileName: string;
}

new Test<A>('id').create(123); // Returns { id: 123 }
new Test<B>('fileName').create('file'); // Returns { fileName: 'file' }

That compiles by inferring TId automatically, does what you want, and enforces the correct key names and values (so filePath as a key or passing a number for fileName won't compile).

Tim Perry
  • 11,766
  • 1
  • 57
  • 85