Lukas Ansteeg
Lukas Ansteeg

Reputation: 341

Is there a clean way to create static inheritable generic constructors in TypeScript?

I'm creating a 'Serializable' abstract class, the children of which I can create with a method call which takes a json object as the argument. I got it to work with the following code, but it results in a rather unwieldy solution.

My current code:

abstract class Serializable {
    public static deserialize<T>(jsonString: string, ctor: { new (): T}) {
        const newCtor = new ctor;
        const jsonObject = JSON.parse(jsonString);
        for (const propName of Object.keys(jsonObject)) {
            newCtor[propName] = jsonObject[propName]
        }
        return newCtor;
    }

    public static deserializeList<T>(jsonString: string, ctor: { new (): T}) {
        let newCtor = new ctor;
        const newArray = new Array<typeof newCtor>();
        const jsonArray = JSON.parse(jsonString)['staff'];
        for (const jsonObject of jsonArray) {
            newCtor = new ctor;
            for (const propName of Object.keys(jsonObject)) {
                newCtor[propName] = jsonObject[propName]
            }
            newArray.push(newCtor);
        }
        return newArray;
    }
}

export class Employee extends Serializable {
    firstName: string;
    lastName: string;
}

I can now create a new instance of Employee like this:

const testJson = '{"firstName": "Max", "lastName": "Mustermann"}';
const testEmployee = Employee.deserialize<Employee>(testJson, Employee);

Ideally, I would like to be able to do this:

const testJson = '{"firstName": "Max", "lastName": "Mustermann"}';
const testEmployee = Employee.deserialize(testJson);

I feel like there should be a way to not have to write 'Employee' three times in one line, but replacing anything with 'typeof this' has gotten me nowhere. I realize this could be avoided by not making the constructor static, but instead having two lines:

const testJson = '{"firstName": "Max", "lastName": "Mustermann"}';
const testEmployee = new Employee();
testEmployee.deserialize(testJson);

But if there is any clean way to do this in one line, I would appreciate an example! I don't fully understand what the ctor: { new (): T} argument does, so my ignorance of a solution might stem from this.

Upvotes: 3

Views: 60

Answers (2)

Nitzan Tomer
Nitzan Tomer

Reputation: 164457

Yeah, you can do that:

abstract class Serializable {
    public static deserialize<T>(this: { new(): T }, jsonString: string): T {
        const newCtor = new (this as any)();
        ...
    }
}

const testEmployee = Employee.deserialize2(testJson); // type of testEmployee is Employee

Notice that there's casting of this to any, that's needed because Serializable is abstract and so the compiler complains that it cannot be instantiated.
If you remove the abstract part then you can also remove this cast.

Also, there's no need to iterate over the properties like that, you can simply use Object.assign:

public static deserialize<T>(this: { new (): T }, jsonString: string): T {
    return Object.assign(new (this as any)(), JSON.parse(jsonString));
}

Upvotes: 3

Bogdan Bogdanov
Bogdan Bogdanov

Reputation: 386

It is about the type.

  abstract class Serializable {
    public static deserialize(jsonString: Employee) {
        const newCtor = new Employee();
        const jsonObject = JSON.parse(jsonString);
        for (const propName of Object.keys(jsonObject)) {
            newCtor[propName] = jsonObject[propName]
        }
        return newCtor;
    }

    public static deserializeList(jsonString: Employee) {
        let newCtor = new Employee();
        const newArray: Employee[] = [];
        const jsonArray = JSON.parse(jsonString)['staff'];
        for (const jsonObject of jsonArray) {
            newCtor = new ctor;
            for (const propName of Object.keys(jsonObject)) {
                newCtor[propName] = jsonObject[propName]
            }
            newArray.push(newCtor);
        }
        return newArray;
    }
}

If you can't rewrite the abstract class, then you are forced to use it the way you already did.

Upvotes: 0

Related Questions