Search code examples
typescriptstructural-typing

Typescript type compatibility test


Does Typescript support a direct test for structural type compatibility?

C# supports the is operator and types surface IsAssignableFrom(object instance)

if (foo is SomeType) ...
if (SomeType.IsAssignableFrom(foo)) ...

Is there some direct way to perform this kind of check in Typescript or do I have to probe for each of the required members?

instanceof will probably do for the case at hand but does not honour structural compatibility, instead checking the prototype chain.

Yes yes, I know, Typescript's instanceof is directly equivalent to C#'s is operator. But I did start by specifying structural typing.

Maybe I should put a quack method on Object


Solution

  • I believe the appropriate answer here is

    No, but!

    So just to get this out of the way, Typescript's type information exists only at compile time. There is no way to do a check of structural equality at runtime. You can do this check at compile time of course but that's just called assignment and probably not what you're asking about.

    Long story short you have to probe for the required members (typically) but there are a few ways to be clever about this.


    As of Typescript 1.6 the language supports user-defined type guards

    These are wonderful as convenience functions and are a way of safely downcasting/specifying/refining a type (depending on your preferred terminology). You can manually check for the presence of certain types if you wish in the typical ways (.hasOwnProperty and such), in my experience they're more effective as ways to dispatch on message types. In most message passing systems you might have a type property. You can define a function

    function isConfigGetMsg(msg: BaseMessage): msg is ConfigGetMsg {
      return msg.name === MsgTypes.CONFIG_GET_MSG;
    }
    
    //and use it as
    if (isConfigGetMsg(msg)){
        console.log(msg.key); //key being a ConfigGetMsg only value
    }
    

    and this will be just fine as long as your messages are well-formed. Though as you can see you'll need to test for specific information. If you'd like a more general approach this aint it.


    As you mentioned you can probe each of the required members. You can make it a bit easier for yourself by using the previous technique to create something like this:

    interface One {
        a: number
    }
    interface Two extends One {
        b: string
    }
    function isAssignableTo<T>(potentialObj:Object, target:T) : potentialObj is T {
        return Object.keys(target).every(key => potentialObj.hasOwnProperty(key))
    }
    
    let a: Object = {
        a: 4,
        b: 'hello'
    };
    
    let b: Two = undefined;
    
    if(isAssignableTo(a, b)) {
        b = a;
    }
    

    the isAssignableTo function being the key point here. The direction the parameters should be in is debatable but also not relevant so put them however you like. This is not an ideal solution. You may want things to be assignable based on only non-function properties, for example, implying state and not common functionality. You could try to extend the checking to account for that but there are pitfalls regardless.


    I'm sorry to say I believe the only "true" answer is one that isn't viable yet. If you're working with classes you can use decorators to capture metadata about your application including type information that can be later used for reflection/inspection. You could create your own decorators to do this but you can also leverage typescript's own metadata generation for class properties. that you can turn on with a tsconfig flag.

    This assumes the metadata reflection api is present. You'll need to use this to extract that information. You could then rewrite the isAssignableTo function from earlier to work off of the metadata. If I remember correctly, this still suffers from the issue that types are stored as strings and if you then have subtypes they won't match.


    Anyway, I hope that helps. Sorry I couldn't give you a simple yes. Knowing my luck you were actually just looking for assignment ;).