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

272 Views Asked by At

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?

1

There are 1 best solutions below

3
VLAZ On BEST ANSWER

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; }