Search code examples
javascriptarraysobjectrecursionlodash

Object traversal function that goes over all attributes (including arrays w/o specified index) and returns last item in given path


I am trying to achieve a similar behavior to lodash's _.get() but with the difference that the function will be able to go over all/any of the arrays in the path instead of specifying the index in the path (ie. 'abc.xyz[0])


    // Example 1
    
    const _object = {abc: {xyz: [{value: 1}, {value: 2}]}};
    
    console.log(accumulateValues(_object, 'abc.xyz.value');
    // Desired output: [{value: 1}, {value: 2}]
    
    // Example 2
    
    const _object = {abc: [{xyz: [{values: 1}]}]};
    
    console.log(accumulateValues(_object, 'abc.xyz.values');
    // Desired output: [1]


Solution

  • A fairly simple approach, if you want [1, 2] out of that might look like this:

    const query = (path, ps = path .split ('.')) => (obj) =>
      ps .reduce ((a, p) => a .flatMap (x => (x || {}) [p]) .filter (Boolean), [obj])
    
    const _object = {abc: [{xyz: [{value: 1}, {value: 2}]}]}
    
    console .log (query ('abc.xyz.value') (_object))

    If you're looking for [{value: 1}, {value: 2}], it probably wouldn't be much harder. But if somehow, as the question seems to imply, you want either result, based upon how you wrap 'abc', then I don't really understand your requirements.

    Here the brackets surrounding some nodes (such as [xyz]) don't add any value, since we simply assume there is an array all the way down, wrapping the initial value in one. If you still wanted to supply them, we could remove them before processing:

    const query = (path, ps = path .replace (/[\[\]]/g, '') .split ('.')) => (obj) =>
    // ...
    

    We might want to improve this to take arrays at the root by checking whether the input was an array already before wrapping it in one. I leave that as an exercise.