Search code examples
typescriptdefinitelytyped

TypeScript Type Annotation Excluding Primitives


The question: Is there any way to write a type annotation in TypeScript that allows any object literal, but doesn't allow built-in types number, string, boolean, Function or Array?

Why?

I have been working on improving some type definitions on DefinitelyTyped for some libraries I am using on my projects at work. A common pattern I have noticed in many JS libraries is to pass an 'options' object used to configure the library or plugin. In these cases, you will often you will see type definitions looking something like this:

declare module 'myModule' {
    interface IMyModule {
        (opts?: IOptions): NodeJS.ReadWriteStream;
    }
    interface IOptions {
        callback?: Function;
        readonly?: boolean;
    }
}

This allows you to use it like this:

var myModule = require('myModule');
myModule();
myModule({});
myModule({ callback: () => {} });
myModule({ readonly: false });
myModule({ callback: () => {}, readonly: false });

Which all are valid use cases. The trouble is that these type definitions also allow for these non-valid use cases:

myModule('hello');
myModule(false);
myModule(42);
myModule(() => {});
myModule([]);

In many cases, the above calls would result in a runtime error, as the library JS code may try to set default values on the object, or pass the options to another library. Although I have tried many things, I haven't been able to constrain the parameter to accept only objects and not any of those other invalid cases.

It seems that if you have an interface with only optional members (as no one option is required), the compiler will widen acceptable types to accept any.

You can see a TypeScript Playground demonstration of this problem here: http://bit.ly/1Js7tLr

Update: An example of a solution that doesn't work

interface IOptions {
    [name: string]: any;
    callback?: Function;
    readonly?: boolean;
}

This attempt requires an indexing operator to be present on the object, which means that it now complains about numbers, strings, booleans, Functions, and Arrays. But this creates a new problem:

var opts = {};
myModule(opts);

This now fails when it should be a valid scenario. (See this in Playground: http://bit.ly/1MPbxfX)


Solution

  • As of TypeScript 2.2, there is now an object type that does almost exactly what I have described above.

    ... you can assign anything to the object type except for string, boolean, number, symbol, and, when using strictNullChecks, null and undefined.

    Check out the official announcement here: Announcing TypeScript 2.2