Leo Jiang
Leo Jiang

Reputation: 26253

Override types for built-in methods on prototype

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

Answers (3)

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
}

Playground

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

Schwarz54
Schwarz54

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

Dennis G.
Dennis G.

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

Related Questions