1

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.

RomanSmoll
  • 81
  • 11
  • 5
    JSON is a *textual notation* for data exchange. [(More here.)](http://stackoverflow.com/a/2904181/157247) If you're dealing with JavaScript source code, and not dealing with a *string*, you're not dealing with JSON. – T.J. Crowder Aug 04 '21 at 10:51
  • I can't work out from the question what the inputs to this function you'd like to have would be, and what its output would be. – T.J. Crowder Aug 04 '21 at 10:53
  • @T.J.Crowder Thank you for reaching out. I think the question is very clear but let me rephrase. How Can i go through multilevel Object literal getting as deep in the levels as i want in a way of one function? Entry parameters for example can be :level where the key is to look for, or anything really...because going For each participant, to Each jobs and then finding the next skill does not look efficient or reliable to me.. Can it be done by some generalized function ? – RomanSmoll Aug 04 '21 at 14:34
  • @T.J.Crowder also, Ok thank you for precising that. Am i correct that I am using OBject literals then ? – RomanSmoll Aug 04 '21 at 14:34
  • The text like `{example: "value"}` that you write in a source file is an object literal. The result in program memory when it runs is an object. What you're trying to do is search through nested objects looking for information. *"Entry parameters can be...or anything else really."* doesn't help us answer the question. :-) – T.J. Crowder Aug 04 '21 at 14:36
  • @T.J.Crowder It is something to learn every day for a junior. Thank you for precising that. Regarding the clearer question, I have just updated it at the end. Does it sound better to you? – RomanSmoll Aug 04 '21 at 14:47
  • Does this answer your question? [Traverse all the Nodes of a JSON Object Tree with JavaScript](https://stackoverflow.com/questions/722668/traverse-all-the-nodes-of-a-json-object-tree-with-javascript) – Heretic Monkey Aug 04 '21 at 14:59
  • Take a look at the npm package object-scan. It does everything you ask for and more. Disclaimer: I'm the author of that package – vincent Aug 17 '21 at 16:04

1 Answers1

1

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;
}
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Hello T.J I am sorry for relplying too late. Your code is honestly quite hard for me at this moment. Can you please explain what does "hasOwn" do ? – RomanSmoll Aug 17 '21 at 07:07
  • @RomanSmoll - :-) It probably would have helped if I hadn't had a really weird mental blip making me write "first" instead of "version" in the comment above it. I've fixed that and added a bunch of comments to the code. Happy coding! – T.J. Crowder Aug 17 '21 at 07:37
  • Thank you for reaching out again :) I ran the code snippet that was provided and un the logs it says at some point , looking at "0"....looking at "1" where 0 and 1 are supposed to be keys...the example object does not have those keys in it. so where do they come form? – RomanSmoll Aug 17 '21 at 08:01
  • 1
    @RomanSmoll - The example object contains an array with two entries. Those are what it's looking at (`example.first2.second2[0]` and `example.first2.second2[1]`). – T.J. Crowder Aug 17 '21 at 08:18
  • T.J, This piece of code is a masterpiece. It is just amazing. Thank you so so much. as a junior I had to struggle to do this "go down " for every value that i needed to research... it was a nightmare. If there are consepts apart form recursion that i need to focus on learning, i'd appreciate an advice!)) Have a great day! – RomanSmoll Aug 17 '21 at 13:57
  • @RomanSmoll - I'm really glad that helped! Recursion really is the key bit here. probably the second, much less key bit is the [`for...in` loop](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in) that lets you loop through the names of an object's properties. Happy coding! – T.J. Crowder Aug 17 '21 at 14:06