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?
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.
The hasOwnProperty() method returns a boolean indicating whether the object has the specified property as its own property (as opposed to inheriting it).
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
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With