4

I am trying to print out which nodes are being accessed via the getter by overriding my objects getter with a proxy. I am trying to basically test which parts of this large object are not being used by my application. The problem I am having is being able to add some way of identifying what a getters parents are. Here is what I have so far

function tracePropAccess(obj) {
  return new Proxy(obj, {
    get(target, propKey, receiver) {
      console.log("Get fired on ", propKey);
      return Reflect.get(target, propKey, receiver);
    }
  });
}

const testObj = {
  a: 1,
  b: {
    a: "no",
    b: "yes"
  },
  c: {
    a: "green",
    b: {
      a: "blue",
      b: "orange"
    }
  }
};

const tracer = tracePropAccess(testObj);

tracer.b.a;

tracer.a;

tracer.c.c.a;

This works great in showing me the prop key - however it only the key from the first level. I am not sure how to approach this with this proxy as this single function overrides all the proxies in the provided object. Is there any way to possibly pass in an objects parents/children? Possibly I am approaching this incorrectly as well - so I am looking for any input. Thanks!

ajmajmajma
  • 13,712
  • 24
  • 79
  • 133

2 Answers2

5

You could use the reflection and check if it is an object. If so, return a proxy, if not, return the value.

It does not work on unfinished objects, because the proxy does not know when an object is returned as result or a proxy is to use

Example:

{ foo: { bar: { baz: 42 } } }

and

tracer.foo.bar

does not work, because it should return

{ baz: 42 }

but it returns a new proxy instead, which leads to strange results. The main problem is to know which more keys are coming and with this notation, it is impossible to know what the next or no key is.

function tracePropAccess(obj) {
    return new Proxy(obj, {
        get(target, propKey, receiver) {
            console.log("Get fired on ", propKey);
            var temp = Reflect.get(target, propKey, receiver);
            return temp && typeof temp === 'object'
                ? tracePropAccess(temp)
                : temp;
        }
    });
}

const testObj = { a: 1, b: { a: "no", b: "yes" }, c: { a: "green", b: { a: "blue", b: "orange" } } };

const tracer = tracePropAccess(testObj);

console.log(tracer.b.a);
console.log(tracer.a);
console.log(tracer.c.c.a);

With path

function tracePropAccess(obj, path) {
    path = path || [];
    return new Proxy(obj, {
        get(target, propKey, receiver) {
            var newPath = path.concat(propKey);
            console.log("Get fired on ", newPath);
            var temp = Reflect.get(target, propKey, receiver);
            return temp && typeof temp === 'object'
                ? tracePropAccess(temp, newPath)
                : temp;
        }
    });
}

const testObj = { a: 1, b: { a: "no", b: "yes" }, c: { a: "green", b: { a: "blue", b: "orange" } } };

const tracer = tracePropAccess(testObj);

console.log(tracer.b.a);
console.log(tracer.a);
console.log(tracer.c.c.a);

With path at the end.

function tracePropAccess(obj, path) {
    path = path || [];
    return new Proxy(obj, {
        get(target, propKey, receiver) {
            var temp = Reflect.get(target, propKey, receiver),
                newPath = path.concat(propKey);
            if (temp && typeof temp === 'object') {
                return tracePropAccess(temp, newPath);
            } else {
                console.log("Get fired on ", newPath);
                return temp;
            }
        }
    });
}

const testObj = { a: 1, b: { a: "no", b: "yes" }, c: { a: "green", b: { a: "blue", b: "orange" } } };

const tracer = tracePropAccess(testObj);

console.log(tracer.b.a);
console.log(tracer.a);
console.log(tracer.c.c.a);
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • This is great - is there a way to know the entire path though? For instance `tracer.c.c.a` console logging something like 'fired on c.c.a' ? Thank you! – ajmajmajma Mar 16 '18 at 18:03
  • @ajmajmajma, only once ath the end, or the whole path walking? – Nina Scholz Mar 16 '18 at 18:11
  • Whole path walking - I am trying to see the entire path of every node on this object that has a get being used on it. My goal is to run it on a **massive** object that I am using currently in my project to see what is not being used. So in this an example I would compile a list of `["b.a", "a","c.c.a"]` or something like this. – ajmajmajma Mar 16 '18 at 18:13
  • A think you could possibly do, albeit a little hacky is. Every time you hit a key, create a setImmidiate(collectPathAndSerialize), which has access to the array that can be passed to `tracePropAccess(_,_,path = [])` with the `path.collector` being set to the thread id. Now when you go down another path, you clear the old thread and reassign a new thread with its ID assigned to `path.collector`. Once the path is accessed, and you yield access from your current execution, it can print the path out. You can completely ditch thread logic if you don't mind all the individual paths being printed out – AP. Mar 16 '18 at 20:00
  • @AP., maybe you add an answer with this suggestion or change my answer, if you like. – Nina Scholz Mar 16 '18 at 20:06
0

I encountered the same requirement and came across this question while researching.

This is solvable by creating a factory function for the proxy traps which recursively prepends the current path within the object:

const reporter = (path = []) => ({
  get(target, property) {
    console.log(`getting ${path.concat(property).join('.')}`)
    const value = Reflect.get(target, property)
    if (typeof value === 'object') {
      return new Proxy(value, reporter(path.concat(property)))
    } else {
      return value
    }
  },
})

const o = new Proxy({
  a: 1,
  b: {
    c: 2,
    d: [ 3 ],
  },
}, reporter())

let tmp = o.a   // 1, logs a
tmp = o.b.c     // 2, logs b, b.c
tmp = o.b.d[0]  // 3  logs b, b.d, b.d.0
dansalias
  • 737
  • 6
  • 11