Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Typescript treat `object.hasOwnProperty("key")` as essentially different from `"key" in object`

Tags:

typescript

declare const action: { total: number } | { };
declare const defavlt: 200;

const total = (action.hasOwnProperty("total")) ? action.total : defavlt;

results in the following TS error for action.total:

Property 'total' does not exist on type '{ type: "NEW_CONVERSATION_LIST" | "UPDATE_CONVERSATION_LIST_ADD_BOTTOM" | "UPDATE_CONVERSATION_LIST_ADD_TOP"; list: IDRArray<RestConversationMember>; total: number | undefined; } | ... 13 more ... | { ...; }'.
  Property 'total' does not exist on type '{ type: "UPDATE_URL_STATE"; updateObj: IMessagingUrlState; }'.ts(2339)

Whereas

const total = ("total" in action) ? action.total : defavlt

works. Is there a rational for TS treating both cases differently?

like image 705
Ben Carp Avatar asked Dec 05 '19 12:12

Ben Carp


People also ask

What's the difference between the in operator and the hasOwnProperty method in objects?

So what's the difference between the two? The key difference is that in will return true for inherited properties, whereas hasOwnProperty() will return false for inherited properties. For example, the Object base class in JavaScript has a __proto__ property, a constructor property, and a hasOwnProperty function.

What does hasOwnProperty mean?

The hasOwnProperty() method returns a boolean indicating whether the object has the specified property as its own property (as opposed to inheriting it).


2 Answers

You can implement your own wrapper function around hasOwnProperty that does type narrowing.

This way you don't have to fiddle with the built in types and do type merging if you don't want to pollute the built in types.

function hasOwnProperty<T, K extends PropertyKey>(
    obj: T,
    prop: K
): obj is T & Record<K, unknown> {
    return Object.prototype.hasOwnProperty.call(obj, prop);
}

I found this solution here: TypeScript type narrowing not working when looping

like image 57
Sámal Rasmussen Avatar answered Sep 21 '22 11:09

Sámal Rasmussen


In the issue microsoft/TypeScript#10485, it was suggested for the in operator to act as a type guard which can be used to filter unions; this was implemented in microsoft/TypeScript#15256 and released with TypeScript 2.7.

This was not done for Object.prototype.hasOwnProperty(); if you really feel strongly about this, you might want to file a suggestion for it, noting that a similar suggestion (microsoft/TypeScript#18282) was declined because it was asking for the more controversial narrowing of the key and not the object... and some people have wanted both (microsoft/TypeScript#20363). And there's no guarantee the suggestion would be accepted.

Luckily for you, though, you don't have to wait for this to be implemented upstream. Unlike an operator like in, the hasProperty() method is just a library signature that can be altered to act as a user-defined type guard function. What's more, you don't even have to touch the standard library definition; you can use declaration merging to augment the Object interface with your own signature for hasOwnProperty():

// declare global { // need this declaration if in a module
interface Object {
  hasOwnProperty<K extends PropertyKey>(key: K): this is Record<K, unknown>;
}
// } // need this declaration if in a module

This definition says that when you check obj.hasOwnProperty("someLiteralKey"), a true result implies that obj is assignable to {someLiteralKey: unknown}, while a false result does not. This definition might not be perfect and there are probably quite a few edge cases (e.g., what should obj.hasOwnProperty(Math.random()<0.5?"foo":"bar") imply? what should obj.hasOwnProperty("foo"+"bar") imply? they will do weird things) but it works for your example:

const totalIn = ("total" in action) ? action.total : defavlt; // okay
const totalOwnProp = (action.hasOwnProperty("total")) ? action.total : defavlt; // okay

Okay, hope that helps; good luck!

Link to code

like image 26
jcalz Avatar answered Sep 19 '22 11:09

jcalz