1

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?

user3840170
  • 26,597
  • 4
  • 30
  • 62
kazon
  • 344
  • 1
  • 4
  • 15
  • In general it's not possible to get any information about where an object is nested, since it can be nested in multiple places. – Barmar Mar 21 '23 at 17:21
  • No, not by using the same `validator` handler everywhere. You'll have to remember where you came from yourself, when you create the `new Proxy` in the `get` trap. – Bergi Mar 21 '23 at 18:28

1 Answers1

1

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; }
VLAZ
  • 26,331
  • 9
  • 49
  • 67
  • Very cool. Not sure it fits my "reasonable" test, but it's plenty close enough to invalidate my short-sighted answer. :-) – T.J. Crowder Mar 21 '23 at 18:38
  • @VLAZ This code would be kind of magic code? If I use it in real world, it would be weird? or you even have used before already in real world? – kazon Mar 22 '23 at 08:18
  • @kazon I honestly can't think of a reason I'd use this code in the real world. Proxies are already rare. And trying to find a "parent property" is *often* [an XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem) since a value is a value regardless of how you access it. You can access `person`, or `obj.firstLevel` or `obj.a.b.c` and they are all the same object. There shouldn't really be distinction how you get to it. However, there might be legitimate uses I can't really think of right now. If you have concerns, I suggest you ask about what you want to do (the X). – VLAZ Mar 22 '23 at 08:23