Skip to content

Search through literal object and all its nested objects (up to nth level) with a generalized function [closed]

Yet, another question about JSON-like Object literals.

the object that I work with has the following structure:

let family ={
"id":"someId",
"participants":[
  {
    "name":"Q",
    "sex":"m",
    "age":23,
    "occupations":[
       {
         "jobId":"some ID"
         "finished": true,
         "start": dateTime,
         "end":dateTime,
         "skills":[
            {
             "name":"welding"
            },
            {
             "name":"concrete mixing"
            },
          ]
       },
       {
         "jobId": "someId",
         "finished": false,
         "skills":[
            {
             "name":"power lifting"
            },
            {
             "name":"swimming"
            },
       {
       }
    ]
  },
{"OTHER Participant"},
{"OTHER Participant"}
]
}

It is for the sake of example.

when I receive data, every literal object is going to be unique so there is no “schema” that I can create and refer to based on the type, for example.

And I need to find the unique objects on the 2nd, 3rd, 4th etc levels.IF they exist

My take on this: Every time I would try to use something either like :

let personIneed; //to save him later;
let part = family.participants;
for (const xx of part){
 let partJobs =xx.occupations;
 for (const xxx of partJobs){
   if(xxx.end && xxx.finished == true){
     let partJobSkills = xxx.skills;
     let isSkillPresent =false; //to change to true once skill iteration is finished
     for (const xxxx of partJobSkills){
       if(xxxx.name ==="concrete mixing"){
          isSkillPresent =true;
       } 
     }
     //check if skill's there and save the participant
     if(isSkillPresent){
       personIneed = xx;
       return;
     }
   } 
 }

}

Which is very bulky and if i have to build a function like that for every set of criteria… well I’d rather build a guillotine. It also looks a little bit like a callback Hell to me (the way it cascades and only) 🙂

Either when the search is only at the high level,

let part = family.participants;
let man = part.find (p=>p.sex==="m");

Which would only return me the first person to match the criteria. which is not ideal.

I would like to build a generalized function that will be able to go through the Object literals finding the nested objects that match my criteria. The criteria should be passed as parameters of the function. Function should also consider that there can be several nested objects with the same keys ( for example one person can be a Builder on many different jobs) or there can be in fact none of the objects that match the criteria. The ultimate goal is somehow to make all the nested objects easily accessible when the object is found. (for example when we found a person who has worked as a builder and who has finished his contract, we return this object but if i need to find the start and end of his working period, I need to go through all the nested objects again and it becomes hell again)

Please note that some arrays will simply not exist and some will have one value.

Answer

From your comments, it sounds like you’re looking for a generalized way to loop through nested objects and arrays. You’d do that with recursion. If you want to make it possible to collect information, or to stop the operation, in different ways for different places you use it, you can pass in a callback function.

Here’s an example:

// A version of `Object.prototype.hasOwnProperty` that we can call with
// any object and key. For arrays, we use this to check for empty slots
// (holes in a sparse arary). For non-array objects, we use this to skip
// inherited properties; in the not-array path below, you could remove
// the `hasOwn` check if you wanted to process inherited properties (but
// probably best to keept it for the array path).
const hasOwn = Function.prototype.call.bind(Object.prototype.hasOwnProperty);

// The deep search function
function deepSearch(obj, callback) {
    if (Array.isArray(obj)) {
        // It's an array, loop through it
        for (let index = 0, len = obj.length; index < len; ++index) {
            // Is there an entry at this index?
            if (hasOwn(obj, index)) {
                // Yes, get its value
                const value = obj[index];
                // Call the callback
                if (callback(obj, index, value)) {
                    // Callback returned a truthy value, stop here and
                    // return true
                    return true;
                }
                // Is this value an object?
                if (value && typeof value === "object") {
                    // Yes, recurse
                    if (deepSearch(value, callback)) {
                        // Recursion found it, stop here
                        return true;
                    }
                }
            }
        }
    } else {
        // It's not an array, loop through the object keys
        for (const key in obj) {
            // Is this an "own" property (not inherited)?
            if (hasOwn(obj, key)) {
                // Yes, get its value
                const value = obj[key];
                // Callback the callback
                if (callback(obj, key, value)) {
                    // Callback returned a truthy value, stop here and
                    // return true
                    return true;
                }
                // Is this value an object?
                if (value && typeof value === "object") {
                    // Yes, recurse
                    if (deepSearch(value, callback)) {
                        // Recursion found it, stop here
                        return true;
                    }
                }
            }
        }
    }
    // Not found, return false
    return false;
}

Here’s a live version using a callback to find something specific and stop the iteration:

const example = {
    first1: "value of first1",
    first2: {
        second1: "value of second1",
        second2: [
            {
                third11: "value of third11",
                third12: {
                    fourth11: "value of fourth11",
                },
            },
            {
                third21: "value of third21",
                third22: {
                    fourth21: "value of fourth21",
                },
            },
        ],
    },
};

// A version of `Object.prototype.hasOwnProperty` that we can call with
// any object and key. For arrays, we use this to check for empty slots
// (holes in a sparse arary). For non-array objects, we use this to skip
// inherited properties; in the not-array path below, you could remove
// the `hasOwn` check if you wanted to process inherited properties (but
// probably best to keept it for the array path).
const hasOwn = Function.prototype.call.bind(Object.prototype.hasOwnProperty);

// The deep search function
function deepSearch(obj, callback) {
    if (Array.isArray(obj)) {
        // It's an array, loop through it
        for (let index = 0, len = obj.length; index < len; ++index) {
            // Is there an entry at this index?
            if (hasOwn(obj, index)) {
                // Yes, get its value
                const value = obj[index];
                // Call the callback
                if (callback(obj, index, value)) {
                    // Callback returned a truthy value, stop here and
                    // return true
                    return true;
                }
                // Is this value an object?
                if (value && typeof value === "object") {
                    // Yes, recurse
                    if (deepSearch(value, callback)) {
                        // Recursion found it, stop here
                        return true;
                    }
                }
            }
        }
    } else {
        // It's not an array, loop through the object keys
        for (const key in obj) {
            // Is this an "own" property (not inherited)?
            if (hasOwn(obj, key)) {
                // Yes, get its value
                const value = obj[key];
                // Callback the callback
                if (callback(obj, key, value)) {
                    // Callback returned a truthy value, stop here and
                    // return true
                    return true;
                }
                // Is this value an object?
                if (value && typeof value === "object") {
                    // Yes, recurse
                    if (deepSearch(value, callback)) {
                        // Recursion found it, stop here
                        return true;
                    }
                }
            }
        }
    }
    // Not found, return false
    return false;
}

deepSearch(example, (obj, key, value) => {
    console.log(`Looking at ${key}, value: ${Array.isArray(value) ? "(array)" : value && typeof value === "object" ? "(object)" : JSON.stringify(value)}`);
    if (key === "third21") {
        console.log(`*** Found third21, value = ${value}`);
        return true;
    }
});
.as-console-wrapper {
    max-height: 100% !important;
}