Reputation: 26253
The default type for hasOwnProperty
is hasOwnProperty(v: PropertyKey): boolean;
. However, this prevents me from doing things like:
const obj = { a: 1 };
function foo(str: string) {
if (obj.hasOwnProperty(str)) {
console.log(obj[str]);
// Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ a: number; }'.
}
}
To override the type for obj.hasOwnProperty
, I added:
interface Object {
hasOwnProperty<T extends ObjectOf<any>>(this: T, key: any): key is keyof T;
}
This works for obj.hasOwnProperty(key)
, but not Object.prototype.hasOwnProperty.call(obj, key)
. How can I override hasOwnProperty
's type when I call it using the second method?
Edit: to clarify, even with the override, the following doesn't work:
const obj = { a: 1 };
function foo(str: string) {
if (Object.prototype.hasOwnProperty.call(obj, str)) {
console.log(obj[str]);
// Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ a: number; }'.
}
}
Upvotes: 2
Views: 1310
Reputation: 33111
FULL UPDATE
I was wrong, I have overriden only call
method.
I'm sorry for providing you with bad example.
With my previous code, calling Object.prototype.[any prototype method].call
would act like typeguard which is wrong.
Previous wrong code:
interface CallableFunction extends Function {
call<T, Prop extends string, R>(this: (this: T, property: Prop) => R, thisArg: T, property: Prop): thisArg is T & Record<Prop, string>;
}
Above code means that Object.prototype.propertyIsEnumerable.call
will act as a typeguard because I have typed only call
.
WORKING EXAMPLE
type Tag = { [prop: `tag${number}`]: never }
interface Object {
hasOwnProperty(v: PropertyKey): boolean & Tag
}
interface CallableFunction extends Function {
call<
T,
Prop extends string,
R extends boolean & Tag
>(this: (this: T, property: Prop) => R, thisArg: T, property: Prop): thisArg is T & Record<Prop, string>;
}
declare const obj: { name?: string, surname?: number }
if (Object.prototype.hasOwnProperty.call(obj, 'name')) {
const test = obj.name // string
}
if (Object.prototype.propertyIsEnumerable.call(obj, 'name')) {
const test = obj.name // string | undefined
}
How does it work ?
I have created a brand type Tag
and intersected it with hasOwnProperty
return type.
What it gives to me?
call
method is able to infer return type of hasOwnProperty
. This is the only way to know which prototype method was called.
Then, I added constraint to R
generic type. R
is an infered return type from prototype method. In our case it is boolean & Tag
. This is how call
method is able to figure out that we have called hasOwnProperty
.
Upvotes: 2
Reputation: 1004
This is what I tried, a little bit dirty, but you get what you want whit this workaround.
const example = {
eProperty:'hello',
e2:1,
e3:[123,432,'paco']
}
foo(str: string) {
if(Object.prototype.hasOwnProperty.call(example, str)){
let index = 0;
for(let k in example){
if(k === str){
break;
}else{
index++;
}
}
// a is a array that index 0 is the key and index 1 is the value
let a = Object.entries(example)[index];
console.log(a[1]);
}
// logs hello
foo('eProperty');
Upvotes: 0
Reputation: 303
Given that its hard to understand your motivation for doing this, here is my best guess at what you're trying to achieve:
interface Object {
hasOwnProperty<T extends ObjectOf<any>>(this: T, key: any): key is keyof T;
}
interface ObjectConstructor {
readonly prototype: Object;
}
if (obj.hasOwnProperty(key)) {
console.log(obj[key]);
}
if ((Object as ObjectConstructor).prototype.hasOwnProperty.call(obj, key)) {
console.log(obj[key]);
}
Without casting:
interface Object {
hasOwnProperty<T extends ObjectOf<any>>(this: T, key: any): key is keyof T;
readonly prototype: this;
}
if (obj.hasOwnProperty(key)) {
console.log(obj[key]);
}
if (Object.prototype.hasOwnProperty.call(obj, key)) {
console.log(obj[key]);
}
Upvotes: 0