How to Find an Object Deeply and Do Something with the Result in JavaScript
Nick Scialli
March 13, 2021
Sometimes we’re in the unenviable position of having to search an object deeply in JavaScript. How can we do this and take some action with the result?
We can take advantage of recursion and callback functions to make this work out pretty well for us.
A Sample Problem
Let’s say we have the following object and we’d like to take some action with the object corresponding to id = 7
.
const obj = {
some: {
props: {
drilling: {
pretty: {
deep: [
{
id: 7,
name: 'Daffodil',
},
],
},
},
},
more: {
props: {
leading: {
to: 'nowhere',
},
},
},
},
};
Our sample problem has one important constrant: the current path of the desired object, obj.some.props.drilling.pretty.deep[0]
is not guaranteed to always be the path. We need a robust way to dive down any number of levels deep and find this object.
The Approach
We will create a recursive function that takes three arguments:
- the object we’re traversing
- a
matcher
function that will evalutate totrue
when we have found the correct object - a callback function that will handle the object once we have found it.
function deepFindCallback(obj, matcher, cb) {
// Magic here
}
Inside the deepFindCallback
function, we will do the following:
- call the
matcher
function on the object to see if we have found our match - if we have found our match, call the callback function
cb
on it - if we have not found our match, recursively call
deepFindCallback
on all children of the current object that are also objects.
function deepFindCallback(obj, matcher, cb) {
// Call the matcher function
// If match, call the callback
// If not match, recursively call deepFindCallback
}
Filling Out the Function
We can fill our function out function in only about 10 lines or so based on our approach.
function deepFindCallback(obj, matcher, cb) {
// Call the matcher function
if (matcher(obj)) {
// If match, call the callback
cb(obj);
}
// If not match, recursively call deepFindCallback
for (let key in obj) {
if (typeof obj[key] === 'object') {
deepFindCallback(obj[key], matcher, cb);
}
}
}
Let’s try it out! Our example states that we want to find the object associated with id = 7
, so we can use the following matcher
function:
const matcher = (obj) => obj.id === 7;
For simplicity, our callback function is going to console.log
the name
associated with this id
.
const cb = (obj) => {
console.log(obj.name);
};
All together, this is what we get:
const obj = {
some: {
props: {
drilling: {
pretty: {
deep: [
{
id: 7,
name: 'Daffodil',
},
],
},
},
},
more: {
props: {
leading: {
to: 'nowhere',
},
},
},
},
};
function deepFindCallback(obj, matcher, cb) {
if (matcher(obj)) {
cb(obj);
}
for (let key in obj) {
if (typeof obj[key] === 'object') {
deepFindCallback(obj[key], matcher, cb);
}
}
}
const matcher = (obj) => obj.id === 7;
const cb = (obj) => {
console.log(obj.name);
};
deepFindCallback(obj, matcher, cb);
// "Daffodil"
As expected, we log "Daffodil"
, which is the name associated with the object that has id = 7
.
Stopping Early
In the above example, our recursive function isn’t going to stop when it finds the object we’re looking for. This could be desired if we’re looking for multiple occurrences; however, we’re often only looking for one occurrence. If that’s the case, we can adjust our function to contain some context about whether the object was found.
function deepFindCallback(obj, matcher, cb) {
let found = false;
finder(obj, matcher, cb);
function finder(obj, matcher, cb) {
if (found) {
return;
}
if (matcher(obj)) {
cb(obj);
found = true;
return;
}
for (let key in obj) {
if (typeof obj[key] === 'object') {
finder(obj[key], matcher, cb);
}
}
}
}
In this case, we add a found
variable and then a new function inside our deepFindCallback
. The new function is created because we need found
to live outside the scope of our recursive function. Within the recursive function we bail out if our object has been found, attempting to perform as little recursion as necessary.
Recursion and Callbacks Are Your Friends
I have found that recursion and callback functions in JavaScript tend to be a powerful combination. In this case, the combo helped us dive arbitrarily deeply down an object and take pretty much any action that we wanted on the found object.
Nick Scialli is a senior UI engineer at Microsoft.