Typescript :: Abstract static

Viewed 3357

In one of my personal projects, I was trying to design a few object and list types. The objects and lists are supposed to be serializable (ie have a toJSON() and fromJSON() method). A sample object and list would have the following basic code:

type IPerson = {
  id: number;
  name: string;
  // additional properties
}

class Person {
  id: number;
  name: string;
  // additional properties
  constructor(id: number, name: string, ...) { ... }
  toJSON(): IPerson { return { ... } }
  static fromJSON(json: IPerson): Person { return new Person(...) }
  // additional methods
}

class PersonList {
  list: Person[];
  constructor(list: Person[]) { ... }
  findById(id: number) { return this.list.find(it => it.id === id) }
  findByName(name: string) { return this.list.find(it => it.name === name) }
  add(person: Person) { this.list.push(person) }
  remove(person: Person) { this.list = this.list.filter(it => it !== person) }
  toJSON(): IPerson[] { return this.list.map(it => it.toJSON()) }
  static fromJSON(json: IPerson[]): PersonList { return new PersonList(json.map(it => Person.fromJSON(it))) }
  // additional methods
}

All the objects and lists I'm working with have at least the methods listed here.

Now I'm trying to convert this into a generic solution such that:

type JSON = {
  id: number;
  name: string;
}

abstract class BaseObject<T extends JSON> {
  abstract get id();
  abstract get name();
  constructor(id: number, name: string) { ... }
  abstract toJSON(): T
  abstract static fromJSON(json: T): BaseObject<T>
}

class BaseList<T, U> {
  list: BaseObject<T>[];
  constructor(list: BaseObject<T>[]) { ... }
  findById(id: number) { return this.list.find(it => it.id === id) }
  findByName(name: string) { return this.list.find(it => it.name === name) }
  add(obj: BaseObject<T>) { this.list.push(obj) }
  remove(obj: BaseObject<T>) { this.list = this.list.filter(it => it !== obj) }
  toJSON(): U[] { return this.list.map(it => it.toJSON()) }
  static fromJSON(json: U[]): BaseList<T, U> { return new BaseList<T, U>(json.map(it => BaseObject<T>.fromJSON(it))) }
}

If this construct worked (which it doesn't), it would make my life as simple as this:

type IPerson = JSON & {
  // additional fields
}

class Person extends BaseObject<IPerson> {
  get id() { ... }
  get name() { ... }
  // additional getters for other fields
  toJSON(): IPerson { return { ... } }
  static fromJSON(json: IPerson): Person { return new Person(...) }
  // additional methods
}

class PersonList extends BaseList<Person, IPerson> {
  // additional methods
}

// other object and list types definitions follow

However, my solution fails at these points:

  1. BaseObject cannot have abstract static fromJSON() method.
  2. BaseList cannot have abstract static fromJSON() method.
  3. BaseList.fromJSON() cannot instantiate a new list, nor can it call BaseObject.fromJSON() to instantiate a new object.

How can I circumvent these issues? Is there a better design pattern that I'm missing here?

1 Answers
Related