Search code examples
javascriptproxyhandlergetter-setter

Get the path to the accessed value in a nested object


I want to get the path to the value in a nested object. But my experiment doesn't work as I expected. The scope should return the path (array of keys) to the exposed value from the nested object but I don't know how to achieve that.

It should work like a watcher which returns the accessed path.

Below is my implementation.

function wrap(o, fn, scope = []) {
  const handler = {
    set(target, prop, value, receiver) {
      fn('set value in scope: ', scope.concat(prop))
      target[prop] = value
      return true
    },
    get(target, prop, receiver) {
      fn('get value in scope: ', scope.concat(prop))
      return o[prop]
    },
    ownKeys() {
      fn('keys in scope: ', scope)
      return Reflect.ownKeys(o)
    }
  }

  return new Proxy(
    Object.keys(o).reduce((result, key) => {
      if (isObject(result[key])) {
        result[key] = wrap(o[key], fn, scope.concat(key))
      } else {
        result[key] = o[key]
      }
      return result
    }, {}),
    handler
  )
}

function isObject(obj) {
  return typeof obj === 'object' && !Array.isArray(obj)
}

const obj = wrap({
  a0: {
    a1: {
      a2: 0
    },
    b1: {
      b2: 0
    }
  },
  b0: 0
}, console.log)


// set value:
obj.b0 = 1

// get value:
console.log('value: ' + obj.a0.a1.a2)

// list keys:
console.log('keys: ', Object.keys(obj.a0))

  • The first log should return set value in scope: ['b0']
  • the second should return get value in scope: ['a0', 'a1', 'a2'] and value: 0
  • the last one should return keys in scope: ['a0'] and the keys of obj.a0

Thanks for any help!


Solution

  • You made a few mistakes :

    get(target, prop, receiver) {
        fn('get value in scope: ', scope.concat(prop))
        return o[prop]
    },
    

    It's return target[prop] to return the wrapped version. and

    if (isObject(result[key])) {
        result[key] = wrap(o[key], fn, scope.concat(
    } else {
        result[key] = o[key]
    }
    

    it's isObject(o[key]) to check on the original object

    And without touching anything else you second log will look more like :

    get value in scope: ['a0'] 
    get value in scope: ['a0', 'a1'] 
    get value in scope: ['a0', 'a1', 'a2'] 
    value: 0