List all possible paths using lodash

Viewed 7251

I would like to list all paths of object that lead to leafs

Example:

var obj = {
 a:"1",
 b:{
  foo:"2",
  bar:3
 },
 c:[0,1]
}

Result:

"a","b.foo","b.bar", "c[0]","c[1]"

I would like to find simple and readable solution, best using lodash.

8 Answers

Based on Nick answer, here is a TS / ES6 imports version of the same code

import {isArray,flatMap,map,keys,isPlainObject,concat} from "lodash";

// See https://stackoverflow.com/a/36490174/82609
export function paths(obj: any, parentKey?: string): string[] {
  var result: string[];
  if (isArray(obj)) {
    var idx = 0;
    result = flatMap(obj, function(obj: any) {
      return paths(obj, (parentKey || '') + '[' + idx++ + ']');
    });
  } else if (isPlainObject(obj)) {
    result = flatMap(keys(obj), function(key) {
      return map(paths(obj[key], key), function(subkey) {
        return (parentKey ? parentKey + '.' : '') + subkey;
      });
    });
  } else {
    result = [];
  }
  return concat(result, parentKey || []);
}

Here is my function. It generates all possible paths with dot notation, assuming there are no property names containing spaces

function getAllPathes(dataObj) {
    const reducer = (aggregator, val, key) => {
        let paths = [key];
        if(_.isObject(val)) {
            paths = _.reduce(val, reducer, []);
            paths = _.map(paths, path => key + '.' + path);
        }
        aggregator.push(...paths);
        return aggregator;
    };
    const arrayIndexRegEx = /\.(\d+)/gi;
    let paths = _.reduce(dataObj, reducer, []);
    paths = _.map(paths, path => path.replace(arrayIndexRegEx, '[$1]'));

    return paths;
}

Here's my solution. I only did it because I felt the other solutions used too much logic. Mine does not use lodash since I don't think it would add any value. It also doesn't make array keys look like [0].

const getAllPaths = (() => {
    function iterate(path,current,[key,value]){
        const currentPath = [...path,key];
        if(typeof value === 'object' && value != null){
            return [
                ...current,
                ...iterateObject(value,currentPath) 
            ];
        }
        else {
            return [
                ...current,
                currentPath.join('.')
            ];
        }
    }

    function iterateObject(obj,path = []){
        return Object.entries(obj).reduce(
            iterate.bind(null,path),
            []
        );
    }

    return iterateObject;
})();

If you need one where the keys are indexed using [] then use this:

    const getAllPaths = (() => {
        function iterate(path,isArray,current,[key,value]){
            const currentPath = [...path];
            if(isArray){
                currentPath.push(`${currentPath.pop()}[${key}]`);
            }
            else {
                currentPath.push(key);
            }
            if(typeof value === 'object' && value != null){
                return [
                    ...current,
                    ...iterateObject(value,currentPath) 
                ];
            }
            else {
                return [
                    ...current,
                    currentPath.join('.')
                ];
            }
        }

        function iterateObject(obj,path = []){
            return Object.entries(obj).reduce(
                iterate.bind(null,path,Array.isArray(obj)),
                []
            );
        }

        return iterateObject;
    })();
const allEntries = (o, prefix = '', out = []) => {
        if (_.isObject(o) || _.isArray(o)) Object.entries(o).forEach(([k, v]) => allEntries(v, prefix === '' ? k : `${prefix}.${k}`, out));
        else out.push([prefix, o]);
        return out;
    };

Array are returned as .0 or .1 that are compatible with _.get of lodash

const getAllPaths = (obj: object) => {
  function rKeys(o: object, path?: string) {
    if (typeof o !== "object") return path;
    return Object.keys(o).map((key) =>
      rKeys(o[key], path ? [path, key].join(".") : key)
    );
  }

  return rKeys(obj).toString().split(",").filter(Boolean) as string[];
};

const getAllPaths = (obj) => {
  function rKeys(o, path) {
    if (typeof o !== "object") return path;
    return Object.keys(o).map((key) =>
      rKeys(o[key], path ? [path, key].join(".") : key)
    );
  }

  return rKeys(obj).toString().split(",").filter(Boolean);
};

const test = {
  a: {
    b: {
      c: 1
    },
    d: 2
  },
  e: 1
}

console.log(getAllPaths(test))

Related