Search code examples
javascriptes6-proxy

Is it possible to get parent property name of nested object in set method of Proxy object?


https://stackoverflow.com/a/41300128
↑ completely based on this code

var validator = {
  get(target, key) {
    if (typeof target[key] === 'object' && target[key] !== null) {
      return new Proxy(target[key], validator)
    } else {
      return target[key];
    }
  },
  set (target, key, value) {
    console.log(target);
    console.log(key); // salary
    console.log(value); // foo
    // ⭐ Is it possible to get "inner" here?
    return true
  }
}


var person = {
      firstName: "alfred",
      lastName: "john",
      inner: {
        salary: 8250,
        Proffesion: ".NET Developer"
      }
}
var proxy = new Proxy(person, validator)
proxy.inner.salary = 'foo'

As I wrote in code with star emoji, is it possible to get parent property name of nested object in set method of Proxy object?

And any alternative solutions you have?


Solution

  • Yes, it is possible by creating new proxy handlers where each keeps the full path which was used to access the property. This is essentially a recursive technique to keep data during recursive calls:

    const makeHandler = (path = []) => ({
      get(target, key) {
        if (typeof target[key] === 'object' && target[key] !== null) {
          return new Proxy(target[key], makeHandler(path.concat(key)))
        } else {
          return target[key];
        }
      },
      set (target, key, value) {
        // ⭐ it is possible to get "inner" here
        console.log("path", path);
        return Reflect.set(...arguments);
      }
    });
    
    var person = {
          firstName: "alfred",
          lastName: "john",
          inner: {
            salary: 8250,
            Proffesion: ".NET Developer"
          }
    }
    var proxy = new Proxy(person, makeHandler());
    proxy.inner.salary = 'foo';

    It will also work if you separate the access and setting to multiple expressions:

    const makeHandler = (path = []) => ({
      get(target, key) {
        if (typeof target[key] === 'object' && target[key] !== null) {
          return new Proxy(target[key], makeHandler(path.concat(key)))
        } else {
          return target[key];
        }
      },
      set (target, key, value) {
        // ⭐ it is possible to get "inner" here
        console.log("path", path);
        return Reflect.set(...arguments);
      }
    });
    
    var person = {
          firstName: "alfred",
          lastName: "john",
          inner: {
            salary: 8250,
            Proffesion: ".NET Developer"
          }
    }
    var proxy = new Proxy(person, makeHandler());
    
    const x = proxy.inner; 
    proxy.firstName; 
    x.salary = "foo";

    Even if you have circular dependencies:

    const makeHandler = (path = []) => ({
      get(target, key) {
        if (typeof target[key] === 'object' && target[key] !== null) {
          return new Proxy(target[key], makeHandler(path.concat(key)))
        } else {
          return target[key];
        }
      },
      set (target, key, value) {
        // ⭐ it is possible to get "inner" here
        console.log("path", path);
        return Reflect.set(...arguments);
      }
    });
    
    const foo = { bar: 1 };
    foo.foo = foo; //circular dependency
    
    var proxy = new Proxy(foo, makeHandler());
    
    proxy.foo.foo.foo.foo.bar = 2;
    console.log("after setting `bar`", foo);

    Or otherwise have multiple paths to a property, this proxy will keep the path that was used to access such property:

    const makeHandler = (path = []) => ({
      get(target, key) {
        if (typeof target[key] === 'object' && target[key] !== null) {
          return new Proxy(target[key], makeHandler(path.concat(key)))
        } else {
          return target[key];
        }
      },
      set (target, key, value) {
        // ⭐ it is possible to get "inner" here
        console.log("path", path);
        return Reflect.set(...arguments);
      }
    });
    
    const person = {
          firstName: "alfred",
          lastName: "john",
          inner: {
            salary: 8250,
            Proffesion: ".NET Developer"
          }
    }
    
    //same object at multiple paths
    const obj = {
      firstLevel: person,
      one: {
        two: {
          three: person
        }
      },
      a: {
        b: {
          c: person
        }
      }
    };
    
    var proxy = new Proxy(obj, makeHandler());
    
    proxy.firstLevel.inner.salary = 1000;
    console.log("after setting `proxy.firstLevel.inner.salary`:", person);
    
    proxy.one.two.three.inner.salary = 2000;
    console.log("after setting `proxy.one.two.three.inner.salary`:", person);
    
    proxy.a.b.c.inner.salary = 3000;
    console.log("after setting `proxy.a.b.c.inner.salary`:", person);
    .as-console-wrapper { max-height: 100% !important; }