21

I have a service which I want to use to return a object of a specified type build from a JSON.

I have a class MyClass which implements a static class which define a FromJSON static method.

export interface InterfaceMyClass {
    static FromJSON(json: any): any;
}

export class MyClass implements InterfaceMyClass {

    constructor(){}

    FromJSON(json: any): MyClass {
        let instance = MyClass.create(MyClass.prototype);
        Object.assign(instance, json);
        // Some other code specific to MyClass
        return instance;
    }
}

I don't know how to call the static method of the generic class that I've passed in parameter in my service. My service looks like this:

export class MyService<T extends InterfaceMyClass> {
    getObject() {
        let json = getExternalJson(...);
        return T.FromJSON(json); // <-- How to call static method FromJSON from T class ?
    }
}

I would like to use the service this way:

let service = new MyService<MyClass>();
let myObject = service.getObject(); // <-- Should by an MyClass instance (created by MyClass.FromJSON)

Question: How can I call this T.FromJSON method?

Bonus question: What is the good way to implements static method? I dont think that my FromJSON method from MyClass is static. If I add static word before FromJSON, it tells me this:

[ts] Class 'MyClass' incorrectly implements interface 'InterfaceMyClass'.
       Property 'FromJSON' is missing in type 'MyClass'.

1 Answer 1

23

A few things:

(1) Interfaces can't have static declarations, for example:

interface MyInterface {
    static myMethod(); // Error: 'static' modifier cannot appear on a type member
}

(code in playground)

The way to get around this in typescript is to define a builder/constructor interface:

interface MyInterface {}

interface MyInterfaceBuilder {
    new (): MyInterface;
    myMethod();
}

(2) Generic constraints are only available in compilation time, but then the compiler removes them as javascript doesn't support it, for example:

class MyClass<T extends string> {
    private member: T;

    constructor() {
        this.member = new T(); // Error: Cannot find name 'T'
    }
}

compiles into:

var MyClass = (function () {
    function MyClass() {
        this.member = new T(); // Error: Cannot find name 'T'
    }
    return MyClass;
}());

(code in playground)

When looking at the js output it's clear why the compiler throws the Cannot find name 'T' error, as T is nowhere to be found.

To get around all of that, here's a solution for you:

interface IMyClass {}

interface IMyClassBuilder<T extends IMyClass> {
    new (): T;
    FromJSON(json: any): any;
}

class MyClass implements IMyClass {
    static FromJSON(json: any) {
        return "";
    }
}

class MyService<T extends IMyClass> {
    private classToCreate: IMyClassBuilder<T>;

    constructor(classToCreate: IMyClassBuilder<T>) {
        this.classToCreate = classToCreate;
    }

    getObject(): T {
        let json = getExternalJson(...);
        return this.classToCreate.FromJSON(json);
    }
}

let service = new MyService(MyClass);
let myObject = service.getObject();

(code in playground)

Sign up to request clarification or add additional context in comments.

2 Comments

I wonder if there could be any way to write this so you could do new MyService<MyClass>() instead of new MyService(MyClass)
The only issue with this solution is that Intellisense doesn't give you an error whenever getObject is not found on an implementation of IMyClass.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.