1

I have an arbitrarily deep object like this:

const objToAccess = {
  id: 1,
  name: {
    first: 'Foo',
    last: 'Bar'
  },
  address: {
    street: {
      name: '987 Walker',
      roadType: 'Ave'
    },
    zip: '12345'
  }
};

I call a function that takes the above objToAccess as a param. EDIT: This function is a black box. I don't know what it looks like and can't edit it. For example:

const accessFn = objToAccess => {
  const a = objToAccess.id;
  const b = objToAccess.name.first;
  const c = objToAccess.address;
};

After that function is called, I want to know which properties were accessed. Here's the kicker: If a nested object was accessed, I want to flag all children as being accessed, too. For example, after running the above code, I would like a result that looks like this:

const propsAccessed = {
  id: true,
  name: {
    first: true,
    last: false
  },
  address: {
    street: {
      name: true,
      roadType: true
    },
    zip: true
  }
};

My naive attempt is to created the propsAccessed object & set everything to false, and then use getters (below). But, I can't figure out how to use them in such a way that flags all children if just the parent object is accessed. Any ideas would be great!

const gettedReturnedMutation = {
  get id() {
    propsAccessed.id = true;
  },
  get name() {
    // TODO if just address is accessed, recursively flag all children as true
    return {
      get first() {
        propsAccessed.name.first = true;
      },
      get last() {
        propsAccessed.name.last = true;
      }
    }
  },
  get address() {
    // TODO if just address is accessed, recursively flag all children as true
    return {
      get street() {
        return {
          get name() {
            propsAccessed.address.street.name = true;
          },
          get roadType() {
            propsAccessed.address.street.roadType = true;
          }
        }
      },
      get zip() {
        propsAccessed.address.zip = true;
      }
    }
  }
}
Matt K
  • 4,813
  • 4
  • 22
  • 35
  • 1
    What is the use case? Reads a bit like an XY problem ... – elclanrs Apr 18 '16 at 14:09
  • Ultimately, i want to remove `false` items from the object. Realistically, the object is an AST. – Matt K Apr 18 '16 at 14:10
  • You could access with your own function `access(obj, 'path')` and perhaps delete the path, that will get rid of children. Not sure why you need to flag children if the parent is flagged and needs to be removed anyway – elclanrs Apr 18 '16 at 14:12
  • the `accessFn` is a blackbox – Matt K Apr 18 '16 at 14:14
  • That's a good thing. – elclanrs Apr 18 '16 at 14:15
  • I guess I don't understand the suggestion. Could you please post an example? – Matt K Apr 18 '16 at 14:16
  • Why is `propsAccessed.name.last` still `false` after the function accessed `objToAccess.name`? I think your requirement doesn't make much sense. You should just flag the `name` property, not its children; if any children are accessed then convert the boolean into an object detailing which child properties were accessed. – Bergi Apr 18 '16 at 14:20
  • @Bergi because only `propsAccessed.name.front` was is used, that's the whole difficulty with the problem. – Matt K Apr 18 '16 at 14:22
  • @MattK: `objToAccess.name.first` are two property accesses, not one - it's `(objToAccess.name).first`, accessing the parent object and accessing the child object. Of course, you can record those separately, and then find out after the call whether only one of them happened. – Bergi Apr 18 '16 at 15:23
  • @Bergi lets say the function called `objToAccess.name` AND `objToAccess.name.first`. – Matt K Apr 18 '16 at 15:42

1 Answers1

2

I would use a Proxy:

function isObject(val) {
  return val === Object(val);
}
function detectAccess(obj) {
  if(!isObject(obj)) return {proxy: obj, accessLog: true};
  var subProxies = Object.create(null);
  var accessLog = Object.create(null);
  var proxy = new Proxy(obj, {
    get: function(target, prop, receiver) {
      if(!accessLog[prop]) {
        var recur = detectAccess(obj[prop]);
        accessLog[prop] = recur.accessLog;
        return subProxies[prop] = recur.proxy;
      } else {
        return subProxies[prop];
      }
    }
  });
  return {proxy, accessLog};
}
var {proxy, accessLog} = detectAccess(objToAccess);
accessFn(proxy);
accessLog; // {id:true, name:{first:true}, address:{}}

The accessLog tells which properties have been accessed. If the value is a primitive, the log will contain true, otherwise it will contain an object.

Then, if you want to know for example if objToAccess.address.street.name has been accessed, use

!!(accessLog.address && accessLog.address.street && accessLog.address.name)

(see test for existence of nested object key to avoid repeating all the property path).

If you want to consider that all the subtree has been accessed when a property of the top object has been accessed, just use

!!(accessLog.address)
Community
  • 1
  • 1
Oriol
  • 274,082
  • 63
  • 437
  • 513
  • this is *almost* right. The only place it fails is if a parent is accessed AND its child. For example: `objToAccess.name` and then `objToAccess.name.first`. would yield `accessLog.name.first = true`, but `last` would also need to be true. Thank you for getting me in the right direction! – Matt K Apr 18 '16 at 19:26
  • @MattK If you want to consider both `.name.first` and `.name.last` as accessed when you use `proxy.name`, then you only need to check `accessLog.name`. – Oriol Apr 18 '16 at 19:54
  • correct me if i'm wrong, but accessing `.name.first` + `.name` returns the same value as accessing just `.name.first`. In the former case, I'd need to also flag `.name.last` as true, but in the latter, `.name.last` should be false. – Matt K Apr 18 '16 at 20:47
  • @MattK You are right. But when you access `.name.first`, you are first accessing `.name` under the hood. That's why there is no difference between `.name.first` + `.name` and `.name.first`. – Oriol Apr 18 '16 at 21:30
  • yep, that's the part that still has me stumped. I think i need to return a `childAccessed` prop & if that comes back false then iterate through all the childProps & give them a `true`. Still haven't figured out how to do that though... – Matt K Apr 18 '16 at 22:04