Javascript Object Recursion To Find An Item At The Deepest Level
Solution 1:
functionfindItem (item, obj) {
for (var key in obj) {
if (obj[key] === item) { // if the item is a property of the objectreturn obj; // return the object and stop further searching
} elseif (Array.isArray(obj[key]) && obj[key].includes(item)) { // if the item is inside an array property of the objectreturn obj; // return the object and stop the search
} elseif (typeof obj[key] === 'object' && !Array.isArray(obj[key])) { // if the property is another objectvar res = findItem(item, obj[key]); // get the result of the search in that sub objectif(res) return res; // return the result if the search was successful, otherwise don't return and move on to the next property
}
}
returnnull; // return null or any default value you want if the search is unsuccessful (must be falsy to work)
}
Note 1:Array.isArray
and Array.prototype.includes
already returning booleans so there is no need to check them against booleans.
Note 2: You can flip the value of a boolean using the NOT operator (!
).
Note3: You have to return the result (if found) immediately after it is found so you won't waste time looking for something you already have.
Note4: The return result of the search will be an object (if found) and since objects are passed by reference not by value, changing the properties of that object will change the properties of the original object too.
Edit: Find the deepest object:
If you want to find the deepest object, you'll have to go throug every object and sub-object in the object obj
and everytime you have to store the object and it's depth (if the depth of the result is bigger than the previous result of course). Here is the code with some comments (I used an internal function _find
that actually get called on all the objects):
functionfindItem (item, obj) {
var found = null; // the result (initialized to the default return value null)var depth = -1; // the depth of the current found element (initialized to -1 so any found element could beat this one) (matched elements will not be assigned to found unless they are deeper than this depth)function_find(obj, d) { // a function that take an object (obj) and on which depth it is (d)for (var key in obj) { // for each ...// first call _find on sub-objects (pass a depth of d + 1 as we are going to a one deep bellow)if (typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
_find(obj[key], d + 1);
}
// then check if this object actually contain the item (we still at the depth d)elseif (obj[key] === item || (Array.isArray(obj[key]) && obj[key].includes(item))) {
// if we found something and the depth of this object is deeper than the previously found elementif(d > depth) {
depth = d; // then assign the new depth
found = obj; // and assign the new result
}
}
}
}
_find(obj, 0); // start the party by calling _find on the object obj passed to findItem with a depth of 0// at this point found is either the initial value (null) means nothing is found or it is an object (the deepest one)return found;
}
Solution 2:
"I'm trying to recursively search an object that contains strings, arrays, and other objects to find an item (match a value) at the deepest level however I'm always getting undefined as the return result."
var foundIt = findItem('glasses', theCobWeb); console.log('The item is here: ' + foundIt); // The item is here: undefined
"The item is here ..." – where?
Well what exactly do you want as a return value? Should it just say "glasses"
when it's all done? In my opinion, that's kind of pointless – fundamentally it's no better than just returning true
or false
.
I wrote this function a while ago because I needed to search a heap of data but also know specifically where it matched. I'd probably revise this a little bit now (or at least include type annotations), but it works as-is, so here you go.
// helpersconst keys = Object.keysconstisObject = x=> Object(x) === x
const isArray = Array.isArrayconstrest = ([x,...xs])=> xs
// findDeepconstfindDeep = (f,x) => {
letmake = (x,ks)=> ({node: x, keys: ks || keys(x)})
letprocessNode = (parents, path, {node, keys:[k,...ks]})=> {
if (k === undefined)
returnloop(parents, rest(path))
elseif (isArray(node[k]) || isObject(node[k]))
returnloop([make(node[k]), make(node, ks), ...parents], [k, ...path])
elseif (f(node[k], k))
return {parents, path: [k,...path], node}
elsereturnloop([{node, keys: ks}, ...parents], path)
}
letloop = ([node,...parents], path) => {
if (node === undefined)
return {parents: [], path: [], node: undefined}
elsereturnprocessNode(parents, path, node)
}
returnloop([make(x)], [])
}
// your sample datavar theCobWeb = {biggestWeb: {item: "comb",biggerWeb: {items: ["glasses", "paperclip", "bubblegum"],smallerWeb: {item: "toothbrush",tinyWeb: {items: ["toenails", "lint", "wrapper", "homework"]}}},otherBigWeb: {item: "headphones"}}};
// find path returns {parents, path, node}let {path, node} = findDeep((value,key)=> value === "glasses", theCobWeb)
// path to get to the item, note it is in reverse orderconsole.log(path) // => [0, 'items', 'biggerWeb', 'biggestWeb']// entire matched nodeconsole.log(node) // => ['glasses', 'paperclip', 'bubblegum']
The basic intuition here is node[path[0]] === searchTerm
Complete path to the matched query
We get the entire key path to the matched data. This is useful because we know exactly where it is based on the root of our search. To verify the path is correct, see this example
constlookup = ([p,...path], x) =>
(p === undefined) ? x : lookup(path,x)[p]
lookup([0, 'items', 'biggerWeb', 'biggestWeb'], theCobWeb) // => 'glasses'
Unmatched query
Note if we search for something that is not found, node
will be undefined
let {path, node} = findDeep((value,key)=> value === "sonic the hog", theCobWeb)
console.log(path) // => []console.log(node) // => undefined
Searching for a specific key/value pair
The search function receives a value
and key
argument. Use them as you wish
let {path, node} = findDeep((value,key)=> key === 'item' && value === 'toothbrush', theCobWeb)
console.log(path) // => [ 'item', 'smallerWeb', 'biggerWeb', 'biggestWeb' ]
console.log(node) // => { item: 'toothbrush', tinyWeb: { items: [ 'toenails', 'lint', 'wrapper', 'homework' ] } }
Short circuit – 150cc
Oh and because I spoil you, findDeep
will give an early return as soon as the first match is found. It won't waste computation cycles and continue iterating through your pile of data after it knows the answer. This is a good thing.
Go exploring
Have courage, be adventurous. The findDeep
function above also gives a parents
property on returned object. It's probably useful to you in some ways, but it's a little more complicated to explain and not really critical for answering the question. For the sake of keeping this answer simplified, I'll just mention it's there.
Solution 3:
That's because the recursive call doesn't assign the return to the variable. And you should check the return from the recursive call and return if true or break from the for loop if you have other logic after it.
functionfindItem(item, obj) {
for (var key in obj) {
if (obj[key] === item) {
return obj;
} elseif (Array.isArray(obj[key]) === true && obj[key].includes(item) === true) {
return obj;
} elseif (typeof obj[key] === 'object' && Array.isArray(obj[key]) === false) {
var foundItem = findItem(item, obj[key]);
if(foundItem)
return foundItem;
}
}
Post a Comment for "Javascript Object Recursion To Find An Item At The Deepest Level"