Without changing the structure, I would do the following:
constcharSeparator="/"/**
* searches deep into an object recursively...
* @param {Object} obj object to be searched
* @param {any} searchValue the value/key to search for
* @param {boolean} [valuesOnly=false] whether to skip comparing object keys
* @param {number} [maxDepth=20] maximum recursion depth (to avoid "Maximum call stack size exceeded")
* @returns {string[]} Paths on the object to the matching results
*/functiongetValuePathInObject(obj,searchValue,valuesOnly=false,maxDepth=20){if(!maxDepth)return[]constpaths=[]for(const[curr,currElem]ofObject.entries(obj)){if(!valuesOnly&&curr===searchValue){// To search for property name too...paths.push(curr)}if(typeofcurrElem=="object"){// object is "object" and "array" is also in the eyes of `typeof`// search again :DconstdeepPaths=getValuePathInObject(currElem,searchValue,valuesOnly,maxDepth-1)constcurrDir=curr+charSeparatorfor(constpathofdeepPaths){paths.push(currDir+path)}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(curr)}}returnpaths}
Use const and let, never var.
Use for of loops instead of imperative key and numeric index iteration.
Remove the debug logging
Hoist the constant charSeparator out of the function (or you could inline the "/" literal)
Only concatenate curr and charSeparator once, not in the loop.
Remove unused variable i.
Use early return (or rather, continue).
Use JSDoc, LMAO xD
Use down-counting depth limit to pass only one number.
Use defaults in the function parameters. These only check for undefined, not truthiness, so passing false and 0 won't trigger them.
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!
Hasn't use "let" before to declare variables, will definitely use now ( Thanks to Sarah Chima for her post "dev.to/sarah_chima/var-let-and-con..." )! :D
Is "for of" more optimized for this kind of methods (recursive), instead of indexed ones ?
I had that "charSeparator" declared inside because at the time i made it i though leaving the option to choose the char used to be received in parameter (but then i just forgot it XD)
That variable "i" .... I just can't even remember why was there ?? :/ My bad !
Tonight i won't be able, but tomorrow right after work, will fix it ! ;)
Without changing the structure, I would do the following:
const
andlet
, nevervar
.for of
loops instead of imperative key and numeric index iteration.charSeparator
out of the function (or you could inline the"/"
literal)curr
andcharSeparator
once, not in the loop.i
.continue
).undefined
, not truthiness, so passingfalse
and0
won't trigger them.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
path
string with no duplication. This includes eliminating the inner loop, since we never return an intermediary array back up. The reasonprefix + 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? :)
Thank you in advance.
Sure, this doesn't depend on it being an arrow function, can be in a
function
just 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
arg
binding, 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
undefined
is provided) is perfectly adequate, and gives defaults for all keys.Finally, at the cost of a tiny bit of memory, you can keep a
Set
of 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!
Hasn't use "let" before to declare variables, will definitely use now ( Thanks to Sarah Chima for her post "dev.to/sarah_chima/var-let-and-con..." )! :D
Is "for of" more optimized for this kind of methods (recursive), instead of indexed ones ?
I had that "charSeparator" declared inside because at the time i made it i though leaving the option to choose the char used to be received in parameter (but then i just forgot it XD)
That variable "i" .... I just can't even remember why was there ?? :/ My bad !
Tonight i won't be able, but tomorrow right after work, will fix it ! ;)
Thanks a lot for your feedback Mihail :D
If you look in the replies to my above comment, I replied to myself (and then replied again to that comment) with new versions of the code.