Home > database >  Is there any way to implement my class in a type-safe manner?
Is there any way to implement my class in a type-safe manner?

Time:06-11

In Typescript, I have two interfaces A and B, both of them having the method .toString() on all of their values. I want to write a class Getter that can take a property name key on construction, and then later be passed an object thing conforming to one of these interfaces, and return thing[key].toString(). I want to do this type-safely, so that if key does not exist on thing, I get an error.

(Note that each Getter will only ever be passed objects of a single interface, known at compile time.)

I can't figure out how to do this: I tried having the type parameter extend A | B where A and B are the two interfaces this class needs to be used on, but that runs into issues, as we could define an interface C that extends A but does not have .toString(). I then tried having it extend Record<string, Stringable> (where Stringable is an interface consisting only of { toString: () => string }) but this doesn't work because Record implies it's acting as a dictionary, which it isn't. I've also tried parameterizing it over a Field type that extends string, but I'm not sure if this would work given that I wouldn't know the value of Field at compile time.

Is this actually possible to do in typescript? If not, are there any alternative methods to what I'm trying to achieve?

(Note also that this is a toy example; in my actual code the class needs to perform an expensive computation on construction and then will be passed tons of objects in a loop, which is why its structured this way.)

Code Example

What I'm trying to do

interface Stringable {
  toString: () => string
}

interface A {
  a: number
}

interface B {
  b: Date
}

class Getter<AB extends /* some type here */> {
  key: keyof AB;

  constructor(key: keyof AB) {
    this.key = key;
  }

  get(thing: AB) {
    return thing[this.key].toString();
  }
}

const bGetter = new Getter<B>('b');
bGetter.get({b: new Date(0,0)});
// "Mon Jan 01 1900 00:00:00 GMT-0800 (Pacific Standard Time)" 

const aGetter = new Getter<A>('b');
// type error

bGetter.get({a: 105});
// type error

First Failure

Changing /* some type here */ to A | B doesn't work because if AB extends A | B then {a: number, c: undefined} satisfies AB.

Second Failure

Changing /* some type here */ in the above to Record<string, Stringable> doesn't work because neither A nor B are compatible with that type.

CodePudding user response:

I believe you can just do:

class Getter<AB extends Record<keyof AB, Stringable>>

Not in a position to test, so let me know if it works or not.

  • Related