Otherwise, I'd use an inner recursive function so that I can do option parsing once, and then close over one array I will always append to, to avoid allocating intermediate arrays:
/**
* searches deep into an object recursively...
* @param {Object} obj object to be searched
* @param {any} searchValue the value/key to search for
* @param {Object} [options]
* @param {boolean} options.[searchKeys] whether to search object keys as well as values. Defaults to `true` if `serchValue` is a string, `false` otherwise.
* @param {number} options.[maxDepth=20] maximum recursion depth (to avoid "Maximum call stack size exceeded")
* @returns {string[]} Paths on the object to the matching results
*/constfindPaths=(obj,searchValue,{searchKeys=typeofsearchValue==="string",maxDepth=20}={})=>{constpaths=[]constnotObject=typeofsearchValue!=="object"constgvpio=(obj,maxDepth,prefix)=>{if(!maxDepth)returnfor(const[curr,currElem]ofObject.entries(obj)){if(searchKeys&&curr===searchValue){// To search for property name too ...paths.push(prefix+curr)}if(typeofcurrElem==="object"){// object is "object" and "array" is also in the eyes of "typeof"// search again :Dgvpio(currElem,maxDepth-1,prefix+curr+"/")if(notObject)continue}// it's something else... probably the value we are looking for// compares with "searchValue"if(currElem===searchValue){// return index AND/OR property namepaths.push(prefix+curr)}}}gvpio(obj,maxDepth,"")returnpaths}
Here, I'm also using an options object for convenience, with a smart default for searchKeys, since object keys are always strings, even in arrays:
I'm also building the path string with no duplication. This includes eliminating the inner loop, since we never return an intermediary array back up. The reason prefix + curr occurs in 3 places is because in a real search, on most keys none of those conditions will happen, and almost never will two happen together.
Not used to see many arrow function (without being small ones, in array.map for example) so i got a bit confused with the parameter line :
{ searchKeys = typeof searchValue === "string", maxDepth = 20 } = {}
Would you mind to explain in a few words why it is there? :)
But that's just more verbose. Destructuring an empty object (if undefined is provided) is perfectly adequate, and gives defaults for all keys.
functionfn(searchValue,// this is the v below that we are depending on, it has to come first.{searchKeys=typeofsearchValue==="string",maxDepth=20}={}){/* ... */}
/**
* searches deep into an object recursively...
* @param {Object} obj object to be searched
* @param {any} searchValue the value/key to search for
* @param {boolean} [searchKeys] whether to search object keys as well as values. Defaults to `true` if `serchValue` is a string, `false` otherwise.
* @returns {string[]} Paths on the object to the matching results
*/constfindPaths=(obj,searchValue,searchKeys=typeofsearchValue==="string")=>{constpaths=[]constvisited=newSet()constnotObject=typeofsearchValue!=="object"constgvpio=(obj,prefix)=>{for(const[curr,currElem]ofObject.entries(obj)){if(searchKeys&&curr===searchValue){paths.push(prefix+curr)}if(typeofcurrElem==="object"){if(visited.has(currElem))continuevisited.add(currElem)gvpio(currElem,prefix+curr+"/")if(notObject)continue}if(currElem===searchValue){paths.push(prefix+curr)}}}gvpio(obj,"")returnpaths}
Disclaimer:
Be careful though, it won't always include the shortest path!
Otherwise, I'd use an inner recursive function so that I can do option parsing once, and then close over one array I will always append to, to avoid allocating intermediate arrays:
Here, I'm also using an options object for convenience, with a smart default for
searchKeys, since object keys are always strings, even in arrays:I'm also building the
pathstring with no duplication. This includes eliminating the inner loop, since we never return an intermediary array back up. The reasonprefix + curroccurs in 3 places is because in a real search, on most keys none of those conditions will happen, and almost never will two happen together.Not used to see many arrow function (without being small ones, in array.map for example) so i got a bit confused with the parameter line :
{ searchKeys = typeof searchValue === "string", maxDepth = 20 } = {}
Would you mind to explain in a few words why it is there? :)
Thank you in advance.
Sure, this doesn't depend on it being an arrow function, can be in a
functionjust as well.This is the equivalent function:
First, we replace the point access with destructuring:
Next, we replace the conditional expressions for defaults with defaults in destructuring AND default in parameter:
Finally, to avoid having to think of a name for the short-lived
argbinding, we can just YEET it right into the parameter list:We could write it like this:
But that's just more verbose. Destructuring an empty object (if
undefinedis provided) is perfectly adequate, and gives defaults for all keys.Finally, at the cost of a tiny bit of memory, you can keep a
Setof visited objects so you can prevent infinite recursion without a counter.Before:
After:
Output:
Code:
Disclaimer:
Be careful though, it won't always include the shortest path!