For a working solution using newer features see this answer https://stackoverflow.com/a/59647842/1323504
I'm trying to write a function where I'd like to indicate that it returns some kind of plain JavaScript object. The object's signature is unknown, and not interesting for now, only the fact that it's a plain object. I mean a plain object which satisfies for example jQuery's isPlainObject
function. For example
{ a: 1, b: "b" }
is a plain object, but
var obj = new MyClass();
is not a "plain" object, as its constructor
is not Object
. jQuery does some more precise job in $.isPlainObject
, but that's out of the question's scope.
If I try to use Object
type, then it will be compatible to any custom object's too, as they're inherited from Object
.
Is there a way to target the "plain object" type in TypeScript?
I would like a type, which would satisfy this for example.
var obj: PlainObject = { a: 1 }; // perfect
var obj2: PlainObject = new MyClass(); // compile-error: not a plain object
Use case
I have kind of a strongly-typed stub for server-side methods, like this. These stubs are generated by one of my code generators, based on ASP.NET MVC controllers.
export class MyController {
...
static GetResult(id: number): JQueryPromise<PlainObject> {
return $.post("mycontroller/getresult", ...);
}
...
}
Now when I call it in a consumer class, I can do something like this.
export class MyViewModelClass {
...
LoadResult(id: number): JQueryPromise<MyControllerResult> { // note the MyControllerResult strong typing here
return MyController.GetResult(id).then(plainResult => new MyControllerResult(plainResult));
}
...
}
And now imagine that the controller method returns JQueryPromise<any>
or JQueryPromise<Object>
. And now also imagine that by accident I write done
instead of then
. Now I have a hidden error, because the viewmodel method will not return the correct promise, but I won't get a compile-error.
If I had this imaginary PlainObject
type, I'd expect to get a compile error stating that PlainObject
cannot be converted to MyControllerResult
, or something like that.
Tested in TypeScript 3.7.2:
For a flat plain object, you can do:
type Primitive =
| bigint
| boolean
| null
| number
| string
| symbol
| undefined;
type PlainObject = Record<string, Primitive>;
class MyClass {
//
}
const obj1: PlainObject = { a: 1 }; // Works
const obj2: PlainObject = new MyClass(); // Error
For a nested plain object:
type Primitive =
| bigint
| boolean
| null
| number
| string
| symbol
| undefined;
type JSONValue = Primitive | JSONObject | JSONArray;
interface JSONObject {
[key: string]: JSONValue;
}
interface JSONArray extends Array<JSONValue> { }
const obj3: JSONObject = { a: 1 }; // Works
const obj4: JSONObject = new MyClass(); // Error
const obj5: JSONObject = { a: { b: 1 } }; // Works
const obj6: JSONObject = { a: { b: { c: 1 } } }; // Works
const obj7: JSONObject = { a: { b: { c: { d: 1 } } } }; // Works
Code is an adaptation from https://github.com/microsoft/TypeScript/issues/3496#issuecomment-128553540