Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I cast to an interface an object may implement?

I have the following classes & interfaces:

export interface IBody {
    body : ListBody;
}

export class Element  {
// ...
}

export class Paragraph extends Element implements IBody {
// ...
}

export class Character extends Element {
// ...
}

I have code where I will get an array of Element derived objects (there are more than just Paragraph & Character). In the case of those that implement IBody, I need to take action on the elements in the body.

What is the best way to see if it implements IBody? Is it "if (element.body !== undefined)"?

And then how do I access it? "var bodyElement = <IBody> element;" gives me an error.

C:/src/jenova/Dev/Merge/AutoTagWeb/client/layout/document/elements/factory.ts(34,27): error TS2012: Cannot convert 'Element' to 'IBody':
    Type 'Element' is missing property 'body' from type 'IBody'.
    Type 'IBody' is missing property 'type' from type 'Element'.

thanks - dave

like image 932
David Thielen Avatar asked Apr 25 '14 00:04

David Thielen


1 Answers

An interface in TypeScript is a compile-time only construct, with no run-time representation. You might find section 7 of the TypeScript specification interesting to read as it has the complete details.

So, you can't "test" for an interface specifically. Done correctly and completely, you generally shouldn't need to test for it as the compiler should have caught the cases where an object didn't implement the necessary interface. If you were to try using a type assertion:

// // where e has been typed as any, not an Element
var body = <IBody> e; 

The compiler will allow it without warning as you've asserted that the type is an IBody. If however, e were an Element in scope, the compiler as you've shown will check the signature of the Element and confirm that it has the properties/methods declared by IBody. It's important to note that it's checking the signature -- it doesn't matter that it may not implement IBody as long as the signature matches up.

Assuming that Element has a signature that matches IBody, it will work. If it does not, you'll get the compiler error you're receiving. But, again, if it's declared as any, the assertion will pass and at run-time, unless the type has the methods defined on IBody, the script will fail.

As your Element is the base class, you cannot check for IBody. You could declare an argument as any:

function someFeature(e: any) {

} 

And then assert that the IBody is present:

function someFeature(e: any) {
     var body :IBody = <IBody> e;    
     // do something
} 

However, if you do need a run-time check, you'd need to look for the function on the prototype or as a property before using it. While that could be misleading in some cases, the interface in TypeScript also may not have caught the mismatch either. Here's an example of how you could check for the existence of a specific function.

It might look like this:

function someFeature(e: any) {
    var body = <IBody> e;
    if (typeof (body.someFunctionOnBodyInterface) === "undefined") {
        // not safe to use the function
        throw new Error("Yikes!");
    }

    body.someFunctionOnBodyInterface();
}
like image 170
WiredPrairie Avatar answered Nov 06 '22 17:11

WiredPrairie