Search code examples
typescripttypescript-genericstypescript-typeses6-proxy

Typescript: function should return proxy object of generic object type


The following function should create a proxy object with a specific handler, but typescript is throwing a type error when I try it that way.

function createProxiedObject<T extends object>(obj: T): T {

  const handler = {
    set(obj: {[key: string]: any}, prop: string, value: any) {
      console.log(`changed ${prop} from ${obj[prop]} to ${value}`);
      obj[prop] = value;
      return true;
    }
  };

  return new Proxy(obj, handler)
}

Error:

Type '{ [key: string]: any; }' is not assignable to type 'T'.
  '{ [key: string]: any; }' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'object'.(2322)

It does not make a difference when I check if obj is of type object.

I can't see why this shouldn't work. It matches with the ProxyConstructor interface:

interface ProxyConstructor {
  // ...
  new <T extends object>(target: T, handler: ProxyHandler<T>): T;
}

What am I doing wrong here?


Solution

  • If we take a look at Proxy's type definition we will see that it accepts a generic parameter:

    interface ProxyConstructor {
        /**
         * Creates a Proxy object. The Proxy object allows you to create an object that can be used in place of the
         * original object, but which may redefine fundamental Object operations like getting, setting, and defining
         * properties. Proxy objects are commonly used to log property accesses, validate, format, or sanitize inputs.
         * @param target A target object to wrap with Proxy.
         * @param handler An object whose properties define the behavior of Proxy when an operation is attempted on it.
         */
        new <T extends object>(target: T, handler: ProxyHandler<T>): T;
    }
    declare var Proxy: ProxyConstructor;
    

    You can manually pass the correct type, instead of letting the typescript do it automatically, thus solutions looks like this:

    function createProxiedObject<T extends object>(obj: T): T {
      const handler = {
        set(obj: { [key: string]: any }, prop: string, value: any) {
          console.log(`changed ${prop} from ${obj[prop]} to ${value}`);
          obj[prop] = value;
          return true;
        },
      };
    
      return new Proxy<T>(obj, handler);
    }
    

    playground