object
primitive typeThis proposal describe a new primitive type for typescript object
.
JavaScript core api contains some functions that takes object
as parameter :
Object.getPrototypeOf
Object.getOwnPropertyDescriptor
Object.create
Currently there is no way in typescript to prevent passing other primitive type (string
, number
, etc ...) to those functions.
For example Object.create('string')
is a perfectly valid typescript statement, even if it will ends up with an error.
Creating a new object
primitive type would allows to model more correctly the signature of those function, and every function that share similar constraint.
The object
type is the equivalent of {}
minus the assignability of other basic type, that means that :
object
object
{}
and any
This behavior is coherant to how javascript works, the type represent every value that respect the constraint typeof value === 'object'
plus undefined
.
A new type guard has to be introduced for object
: typeof value === 'object'
.
Optionally the compiler could also accept comparaison with the Object
function cast : Object(value) === value
.
It'd be useful to list some APIs other than the Object.
functions which would benefit from this.
In the javascript core api except from Object
only perhaps some es6 constructs will benefit from this (WeakMap, Proxy etc).
But for example every underscore collections function would also gain better typing, framework like React use a 'setState' method that accept only object or null
, ImmutableJS, Mori etc etc.
Edit: underscore collection work with any types
Discussed at suggestion review meeting. This all makes sense, but we need to justify the complexity of a new primitive type with the number of errors / richness of description it is able to provide. Basically, more examples of APIs that can't operate on primitives, especially ones that aren't "expert" APIs (e.g. Proxies)
Posting more as reference than recommendation :wink:
interface String { _primitiveBrand?: string; }
interface Boolean { _primitiveBrand?: boolean; }
interface Number { _primitiveBrand?: number; }
interface ObjectOnly { _primitiveBrand?: void; }
function fn(x: ObjectOnly) { }
// Error
fn(43);
// Error
fn('foo');
// OK
fn({});
// OK
fn(window);
+1 on this proposal. I wouldn't consider WeakMap
an "expert" API, but I do consider it reason enough alone to implement this. Since the runtime makes a distinction between object types and primitive types, it only makes sense to have a facility in TypeScript to make the distinction as well.
This proposal will also solve the problem of typing "object bags" where each property is optional, but other primitive types should be disallowed.
Object.observe
requires a non-primitive target as well: http://arv.github.io/ecmascript-object-observe/#Object.observe
I think the title is a bit confusing since it mentions the word "primitive". This could alternatively be called the _"ECMAScript object type"_ or the _"Runtime object type"_. Which would be assignable from anything other than primitive types (including undefined
type and symbols), function or constructor types. The criteria for what is a legal object
type should be based on the following guard:
let isObject (x) => typeof x === "object";
According to MDN, this is the output of typeof
:
Undefined: "undefined"
Null: "object" (see below)
Boolean: "boolean"
Number: "number"
String: "string"
Symbol (new in ECMAScript 2015): "symbol"
Host object (provided by the JS environment): Implementation-dependent
Function object (implements [[Call]] in ECMA-262 terms): "function"
Any other object: "object"
Without this type, there is no way to correctly match a precise type for the above mentioned guard, not even as a user-defined guard. This is also essential for use with any function that strictly requires an object that has properties and allows for in
loops and calls to the Object
prototype, and for implementing generic constraints like T extends object
and then safely referencing properties or using the prototype functions.
Having functions and constructors excluded, though, would be a bit limiting in some cases, so there could be another type that would include them as well and called the _"non-primitive object type"_. A partial workaround could be the union object | Function
though casts or additional guards would be needed to handle it correctly.
Accepting PRs. This seems to be a common problem; this will be a breaking change for anyone brave enough to have written interface object { ... }
. Implementation should be straightforward and follows the rules outlined above.
Side note: We're all wondering where @fdecampredon has been!
@RyanCavanaugh Change of town of work, and unfortunately not a lot of typescript in my new job :D
Great. I want to use this type in TS1.8.
I would also like to see something like an object
type. However, I question whether it should conform to typeof value === 'object'
, as this would exclude functions. Instead, it seems to me that the prospective object
type should reflect the Object language type, which includes functions. There are a few reasons for this:
Object
constructor.I'll take these in order.
APIs such as WeakMap
that only take objects accept the Object language type, not just plain objects. The following is ok:
function theAnswer() {}
let map = new WeakMap();
map.set(theAnswer, 42);
console.log(map.get(theAnswer)); // 42
This is as true for custom APIs as it is for built-in APIs. If I expect an object, I usually just want a bundle of properties. Functions are just callable bundles of properties.
Since Function
extends the Object
constructor, we can use the static and instance methods available on Object
on functions as well. For example, the following is possible:
class DeepThought {
static getAnswer() {
return 42;
}
}
let computer = Object.create(DeepThought);
console.log(computer.getAnswer()); // 42
console.log(Object.getPrototypeOf(computer)); // [Function: DeepThought]
While the example above is a bit silly, it just illustrates that APIs that use the Object
methods internally could just as well accept functions.
So I would suggest that the object
type conform to the following, which corresponds to the Object language type (except that it includes null
, but there's no guarding against null
in TypeScript in any case).
function isObject(value) {
return typeof value === 'object' || typeof value === 'function';
}
I have a project called Deep Map that recurses through the key-value pairs of nested objects and transforms primitive values along the way. As such, it distinguishes between primitive and non-primitive types. I had to define a custom NonPrimitive
interface, as follows:
interface NonPrimitive {
[key: string]: any;
[index: number]: any;
}
export class DeepMap {
// ...
private mapObject(obj: NonPrimitive): NonPrimitive { /* ... */ }
}
This is not the first time I've had to define the NonPrimitive
interface. It would be nice if I could just do this:
export class DeepMap {
// ...
private mapObject(obj: object): object { /* ... */ }
}
As I suggested above, I don't really care whether the obj
parameter is callable or not. All that matters is that it is of the Object
_language type_, i.e., that it is a bundle of properties whose key-value pairs I can iterate through.
I would agree that functions are object
s. The intent is to exclude primitives.
Ok, I thought that must have been the intent. I just thought maybe I was missing something with all this talk of typeof value === 'object'
.
would this object
type allow ad-hoc property assignment (like any
)? For example:
const foo: object = {};
foo.bar = 'baz';
No
What's the practical difference between
export class DeepMap {
// ...
private mapObject(obj: object): object { /* ... */ }
}
and
export class DeepMap {
// ...
private mapObject(obj: Object): Object { /* ... */ }
}
? (Using object
vs Object
). It seems to me that a Function
extends from Object
, so I don't see why we can't already do that. Can you explain?
@trusktr All values other than null
and undefined
are assignable to the Object
type; a function that accepts an Object
will take a string, number, boolean, or symbol. Only non-primitive values are assignable to object
; passing a string, etc, will produce a compile-time error. The object
type is useful where we expect a non-primitive value, but where we don't care what its properties are.
Most helpful comment
+1 on this proposal. I wouldn't consider
WeakMap
an "expert" API, but I do consider it reason enough alone to implement this. Since the runtime makes a distinction between object types and primitive types, it only makes sense to have a facility in TypeScript to make the distinction as well.This proposal will also solve the problem of typing "object bags" where each property is optional, but other primitive types should be disallowed.