user606521
user606521

Reputation: 15434

How to enforce argument to be instance of constructor/class that is property of some object?

Here is my code. Generally class keeps map of other classes and in a method I want to enforce that given key is accompanied by class instance which is instance of class kept in map at key. In comments you can see where ts shows errors.

abstract class AbstractSerializable {
  public abstract run(): void;
}

interface ISerializable {
  serialize: (cls: AbstractSerializable) => any;
  deserialize: (json: any) => AbstractSerializable;
}

type ClassMapItem = (new (...args: any[]) => AbstractSerializable) & ISerializable;

interface ClassMap {
  [key: string]: ClassMapItem;
}

abstract class AbstractClassManager<T extends ClassMap> {
  public readonly classMap: T;

  public serializeInstance<K extends keyof T>(
    key: K & string,
    instance: T[K], // <- I want to enforce "instance" to be instance of this.classMap[key]
  ): any {
    const Constructor = this.getClass(key);
    const json = Constructor.serialize(instance); // ERROR: Property 'run' is missing in type 'ClassMapItem'
    return json;
  }

  public getClass<K extends keyof T>(key: K & string) {
    return this.classMap[key];
  }
}

class SomeClass extends AbstractSerializable {
  public static serialize(c: SomeClass) {
    return {};
  }
  public static deserialize(json: any) {
    return new SomeClass();
  }

  public run() {
    return;
  }
}

const classMap: ClassMap = {
  someClass: SomeClass,
};

class ClassManager extends AbstractClassManager<typeof classMap> {

}

const manager = new ClassManager();
manager.serializeInstance('someClass', new SomeClass()); // ERROR: Type 'SomeClass' provides no match for the signature 'new (...args: any[]): AbstractSerializable'

Upvotes: 1

Views: 62

Answers (1)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 249716

To get the instance type from a constructor you can use the predefined InstanceType conditional type:

abstract class AbstractClassManager<T extends ClassMap> {
    public readonly classMap!: T;

    public serializeInstance<K extends keyof T>(
        key: K & string,
        instance: InstanceType<T[K]>, // The type of the instance T[K] returns
    ): any {
        const Constructor = this.getClass(key);
        const json = Constructor.serialize(instance);
        return json;
    }

    public getClass<K extends keyof T>(key: K) {
        return this.classMap[key];
    }
}

Upvotes: 2

Related Questions