javascriptobjectnested-properties

A way to access a property without knowing its path in a nested js object


is there a way to access a nested property within an object without knowing its path? For instance I could have something like this

let test1 = {
  location: {
    state: {
     className: 'myCalss'
    }
 }
};

let test2 = {
  params: {
    className: 'myCalss'
  }
};

Is there neat way to 'extract' className property? I have a solution but it's pretty ugly, and it accounts just for this two cases, I was wondering if there is something more flexible I could do


Solution

  • Here's a somewhat elegant approach to creating nested property getters:

    const getProperty = property => {
      const getter = o => {
        if (o && typeof o === 'object') {
          return Object.entries(o)
            .map(([key, value]) => key === property ? value : getter(value))
            .filter(Boolean)
            .shift()
        }
      }
    
      return getter
    }
    
    const test1 = {
      location: {
        state: {
          className: 'test1'
        }
      }
    }
    
    const test2 = {
      params: {
        className: 'test2'
      }
    }
    
    const test3 = {}
    
    const getClassName = getProperty('className')
    
    console.log(getClassName(test1))
    console.log(getClassName(test2))
    console.log(getClassName(test3))

    If you want to prevent cyclical objects from causing a stack overflow, I suggest using a WeakSet to keep track of iterated object references:

    const getProperty = property => {
      const getter = (o, ws = new WeakSet()) => {
        if (o && typeof o === 'object' && !ws.has(o)) {
          ws.add(o)
          return Object.entries(o)
            .map(([key, value]) => key === property ? value : getter(value, ws))
            .filter(Boolean)
            .shift()
        }
      }
    
      return getter
    }
    
    const test1 = {
      location: {
        state: {
          className: 'test1'
        }
      }
    }
    
    const test2 = {
      params: {
        className: 'test2'
      }
    }
    
    const test3 = {}
    const test4 = {
      a: {
        b: {}
      }
    }
    
    test4.a.self = test4
    test4.a.b.self = test4
    test4.a.b.className = 'test4'
    
    const getClassName = getProperty('className')
    
    console.log(getClassName(test1))
    console.log(getClassName(test2))
    console.log(getClassName(test3))
    console.log(getClassName(test4))