Aviel Fedida
Aviel Fedida

Reputation: 4102

Typescript, accurately represent class method behavior

I have few interfaces and a class which takes an optional options object:

interface asObject { a: number, b: number }

interface options { returnAs: string; }

interface IA {
    go(): string;
    go(o: options): string | asObject;
}

class A implements IA {
    public go(o?: options): string | asObject {
        if(o&& o.returnAs && typeof o.returnAs === 'string') {
            switch(o.returnAs) {
                case 'object':
                    return { a: 5, b: 7 };
                default:
                    return 'string';

            }
        }
    }
}

And the error I get: "class A incorrectly implements interface IA".

If I try to overload a method:

...
public go(): string;
// Notice the parameter is no longer optional, ? removed.
public go(o: options): string | asObject { /* implementation as above */ }
...

Now I get: "Overload signature is not compatible with function implementation".

I know I can just remove the overloaded signature on the IA interface and remove the overloaded method on A class:

// Interface IA, notice the parameter is now optional, ? added.
go(o?: options): string | asObject;
// Class A
public go(o?: options): string | asObject { /* implementation as above */ }

Let me explain:

The A class have a method called go, if go won't be provided with options object it will return a string, but if a user provides an options object, the return value depends on the returnAs field, meaning a string or an object.

My question:

I don't think the solution I provided represent accurately the behavior of the go method.

Is there a way to preserve the accurate behavior for the sake of typescript usage and not getting errors as I did with my first 2 tries described above?

When I say accurate behavior I mean:

I'm looking for a way where typescript will be able infer AObject type as string:

var AObject = new A().go();

And it will be able to infer AObject as either string or asObject:

var AObject = new A().go({ returnAs: 'object|string' });

I'm not 100% sure it's possible in typescript, in that kind of case I'd be glad for a suggestion.

Upvotes: 3

Views: 191

Answers (1)

Artem
Artem

Reputation: 1870

The easiest way is to declare A.go result as any:

public go(o?: options): any {

Or declare function interface:

interface asObject { a: number, b: number }

interface options { returnAs: string; }

interface IGoFunction {
    (): string;
    (o: options): string | asObject;
}

interface IA {
    go: IGoFunction;
}

class A implements IA {
    go = <IGoFunction>function (o?: options): string | asObject {
        if (o && o.returnAs && typeof o.returnAs === 'string') {
            switch (o.returnAs) {
                case 'object':
                    return { a: 5, b: 7 };
                default:
                    return 'string';
            }
        }
    }
}

Actually you do not even need to declare named interfaces:

class A {
    go = <{
        (): string;
        (o: options): string | asObject;
    }>function (o?: options): string | asObject {
    ...

Downside is that the function added to each instance of A, but you can explicitly add it to prototype:

class A {
    go: {
        (): string;
        (o: options): string | asObject;
    };
}

A.prototype.go = function (o?: options): any {
...

Upvotes: 3

Related Questions