Typescript: Less restrictive object type

Created on 3 Sep 2017  路  20Comments  路  Source: microsoft/TypeScript

New behavior

No property existence check for object type.

let foo: object;
foo.bar; // no error

Rationale

The current object type is useful when used to type function arguments but not very useful on object members. See this IDL:

[SecureContext]
interface PaymentResponse {
    [Default] object        toJSON();

    readonly attribute DOMString       requestId;
    readonly attribute DOMString       methodName;
    readonly attribute object          details;
    readonly attribute PaymentAddress? shippingAddress;
    readonly attribute DOMString?      shippingOption;
    readonly attribute DOMString?      payerName;
    readonly attribute DOMString?      payerEmail;
    readonly attribute DOMString?      payerPhone;

    Promise<void> complete(optional PaymentComplete result = "unknown");
};

Here, the details member can be any "object" depending on payment method behavior. Current lib.d.ts defines this member as any so that a user can access any untyped members.

let response: PaymentResponse;
let { details } = response;
details.anImplementationDependentMemberName; // great

But any means it can be boolean, number, string or even null or undefined.

if (typeof details === "number") {
  // `details` is resolved as `number` but really should be `never` here
}
details++; // no error, :(

A language service should be able to warn on these cases while still allowing arbitrary member accesses. Allowing such free accesses on object type will help here:

details.anImplementationDependentMemberName; // still great
if (typeof details === "number") {
  // it cannot be a number, so `never` here
}
details++; // error now :D

Hey, a function can also be an object in TS!

Yes, but I don't think the fact will cause any problem as functions can also have arbitrary members. And I'm not proposing arbitrary function call.

Needs More Info

Most helpful comment

Seems like we should have a FAQ entry for the differences between {}, { [s: string]: any }, object, and any

All 20 comments

Isn't this proposed type equivalent to {[k: string]: any} ?

https://www.typescriptlang.org/play/index.html#src=type%20Obj%20%3D%20%7B%20%5Bk%3A%20string%20%5D%3A%20any%20%7D%0A%0Avar%20a%3A%20Obj%20%3D%201%0Avar%20b%3A%20Obj%20%3D%20true%0Avar%20c%3A%20Obj%20%3D%20%27ss%27%0Avar%20d%3A%20Obj%20%3D%20function%20()%20%7B%20%7D%20%0Avar%20e%3A%20Obj%20%3D%20new%20Number(3)

See also #10715 and #12416

Looks like this should be covered by #10715

I think the main difference between this and the proposed unknown type is that unknown never ensures that the object is a non-primitive one, so I don't think this is a duplicate of #10715.

I do not think #10715 is about unknown. it is not clear what unknown really means. i think it is mainly about adding properties to a type by asserting them. if were to do this we would do it on object. I am assuming this is the underlying reason for this proposal, wanting to check for a JSON object properties, and assert their existence.

Asking for unchecked property access on object but not wanting to use any seems a bit unbalanced to me. details.foo++ is more or less the same as details++.

I'm not proposing adding properties by assertion, I'm rather proposing a type starting as object but doesn't have property existence check:

var o: object;
o.anything.is.possible.without.assertion;

var n: number;
n = o; // but not this, because `o` is a non-primitive object.

This basically makes object as "a union type of all possible object types", when any is a union type of all types.

so why not any? your argument about safety is compromised by making object property access unchecked.

I want to keep the type safety of the base object type, not the types of its properties.

function foo(bar: object) {
  return bar;
}

// Here, you can put anything on the argument if it is a non-primitive object
const f = foo({ arbitrary1: 0, arbitrary2: 1 });

// Now f is an object with arbitrary properties, but suddenly TS warns
f.arbitrary1
//~~~~~~~~~~ Property `arbitrary1` does not exist on type `object`.

If I do any:

f.arbitrary1 // No problem

1 + f.arbitrary1; // Okay, do it
1 + f; // ??

not sure i follow..

var f: object;

f.arbitrary1 // Error: Property 'arbitrary1' does not exisit on type 'object'

1 + f.arbitrary1; // still an Error
1 + f; // Error: Operator '+' can not be applies to '1' and 'object'

this seems uniform to me.

this seems uniform to me.

Yes, but that's not a useful behavior. It just acts as an empty interface {} does.

var f: {};

f.arbitrary1 // Error: Property 'arbitrary1' does not exisit on type 'object'

1 + f.arbitrary1; // still an Error
1 + f; // Error: Operator '+' can not be applies to '1' and 'object'

My proposal here is to represent an object type that can have any properties, say a user-defined configuration object bag that is later exposed as a member.

BTW, @HerringtonDarkholme, what are the key differences between object and {[k: string]: any} when it is used on function arguments? The code in TS2.2 release note works also with the latter.

declare function create(o: {[k: string]: any} | null): void;

create({ prop: 0 }); // OK
create(null); // OK

create(42); // Error
create("string"); // Error
create(false); // Error
create(undefined); // Error

Yes, but that's not a useful behavior. It just acts as an empty interface {} does.

well, it is safe. if you want the open to all kinda object, use any.

My proposal here is to represent an object type that can have any properties, say a user-defined configuration object bag that is later exposed as a member.

how is that different from { [x:string]: any }?

how is that different from { [x:string]: any }?

That's my new question, what's the purpose of object type when it is for function arguments (at least #1809 and TS2.2 release note describe so) and { [x:string]: any } can be used there?

object is useful in an input position to signal something that is not a primitive. it is also useful when you have something you want to treat like a black box. { [x:string]: any} has more assumptions about the type. namelly that all its properties is of type any. it is similar to the difference between {} and any.

If one wants to use object to type functions written in TS then it makes sense, but the discussion on #1809 and the OP on #12501 show that it was to type existing JS functions including Object.create.

Why did TS team decide to introduce a new type when we can use static create(proto: { [x:string]: any }); and the resulting behavior is same?

In other words, do these functions have different behaviors?:

declare function foo(input: object): void;
declare function foo(input: { [key: string]: any }): void;

they are close, but they are not the same. object is more restrictive than { [key: string]: any } see https://github.com/Microsoft/TypeScript/issues/1809 for more details.

Still not sure, can you kindly give me an example where declare function foo(input: { [key: string]: any }): void; accepts when declare function foo(input: object): void; rejects?

for a function parameter they should behave the same way. however, a function taking { [x:string]: any } contract allows for accessing its members, where as object is more of a black box. it depends on what you want to do with them. if is it is a property bag that you expect use { [x:string]: any }, if it is an opaque object that you just want to hold on for identity use object. for constraints to ensure that you are not getting number or string, use T extends object just cause it is shorter and more descriptive.

Hmm, I see. Thank you for your explanation.

Seems like we should have a FAQ entry for the differences between {}, { [s: string]: any }, object, and any

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Roam-Cooper picture Roam-Cooper  路  3Comments

manekinekko picture manekinekko  路  3Comments

blendsdk picture blendsdk  路  3Comments

uber5001 picture uber5001  路  3Comments

dlaberge picture dlaberge  路  3Comments