Reputation: 2160
I have a sample object:
var test = {obj: check, oi: "5" }
var lord = {sol: "5"};
var holand = {vol: "try", mice: lord}
var second = {bol: "true"};
var check = { fol: holand}
my question is how to retrieve if object "test" has a property of "sol" which is in another object several layers deep. Should it be a somewhat recursive function? Is it possible to retrieve some kind of path to the property if the function returns true (property exists). In this case the path should be test["obj"]["fol"]["mice"]
Upvotes: 0
Views: 149
Reputation: 115
Here is a (mostly) pure functional approach to recurse with some sort of 'condition':
/**
Recurse through object until condition is met.
@param {Array} objs An array of objects
@return {Boolean} Whether condition is met.
*/
const recurseObjs = (objs, condition) => {
const _subObjs = []
for (let obj of objs) {
for (let val of Object.values(obj)) {
if (val === condition /*replace with some condition or multiple*/) return true // Stop if your condition is found
else if (typeof val === 'object' && val !== null && Object.prototype.toString.call(val) !== '[object Array]') _subObjs.push(val) // Else recurse through subobjects that are not array or null
}
}
if (_subObjs.length > 0) return recurseObjs(_subObjs, condition);
return false // Default return value if condition not found
}
recurseObjs([{foo:'bar'},{hello:{cruel:'world'}}],'world') // => true
recurseObjs([{foo:'bar'},{hello:{cruel:'world'}}],'test') // => false
If having the full path of the object's "found" subproperty value is needed, you can instead create a new key value _parent
to add to the subproperty object. This _parent
will be a string which holds the full subproperty path. It is updated on each recursion before being passed to the callback function. Then, _path
can be returned instead of the true
condition.
Upvotes: 0
Reputation: 48247
Since originally answering this question, I was inspired to work on a library to do just this: koalaesce.
It works something like:
var test = {
obj: check,
oi: "5"
}
var lord = {
sol: "5"
};
var holand = {
vol: "try",
mice: lord
}
var second = {
bol: "true"
};
var check = {
fol: holand
}
function getDescendent(base, steps) {
var step = base;
for (var i = 0; i < steps.length; ++i) {
if (step.hasOwnProperty(steps[i])) {
step = step[steps[i]];
} else {
throw new Error("Missing link at " + steps[i]);
}
}
return step;
}
document.write(getDescendent(check, ["fol", "mice", "sol"]));
try {
document.write(getDescendent(check, ["fol", "tofu", "sol"]));
} catch (e) {
document.write(e);
}
The algorithm here starts from a base
object and works its way down, going through an array of strings representing the property name of each step. It checks to make sure the current object has its own property (not an inherited one) with the appropriate name, then walks down to that level. If the object does not, it throws with the missing name.
There are a number of occasions where you may want to remove the hasOwnProperty
check, as inherited/prototype properties and methods may be useful (and it won't hurt anything). In your case, with simple objects, the behavior won't change. In most cases, not checking will allow you to access more properties.
For a more clever, ES6 solution, you can also use reduce
and:
let test = {
obj: check,
oi: "5"
}
let lord = {
sol: "5"
};
let holand = {
vol: "try",
mice: lord
}
let second = {
bol: "true"
};
let check = {
fol: holand
}
function getDescendent(base, ...steps) {
return steps.reduce((prev, cur) => {
if (prev && prev.hasOwnProperty(cur)) {
return prev[cur];
} else {
throw new Error("Missing link at " + cur);
}
}, base);
}
document.write(getDescendent(check, "fol", "mice", "sol"));
document.write(getDescendent(check, "fol", "tofu", "sol"));
Upvotes: 2
Reputation: 29436
You must have a proper idea what you want to check. I.e. the path down the object hierarchy. Because if you just started iterating all properties (keys) recursively, it won't be efficient at first place and there is a good chance of getting into looped references.
Nevertheless, you can query object properties, and property's properties down the hierarchy:
/**
* Get a property defined under given object's hierarchy, matching given path.
* @param obj Object to look into.
* @param path filesystem style path for properties. E.g '/prop1/prop2/prop3'
* @returns {*} Property under object hierarchy, or undefined.
*/
function getProp(obj,path){
var props = path.split('/').reverse();
var p;
var o = obj;
while( p = props.pop()){
o = o[p]; // you can use your preferred scope. i.e own property only
if(o == undefined){
return o;
}
}
return o;
}
/**
* Utility function to check if a property is defined under given object's hierarchy, matching given path.
* @param obj Object to look into.
* @param path filesystem style path for properties. E.g '/prop1/prop2/prop3'
* @returns {boolean} true if property is defined, false if property is undefined.
*/
function hasProp(obj, path){
return getProp(obj,path) != undefined;
}
// Test object
var testObj = {
a:{
b:{
c:'hello',
d:{
e:'world'
}
}
}
};
// valid property paths
console.log(hasProp(testObj,'a/b/c/'));
console.log(hasProp(testObj,'a/b/d/e'));
console.log(getProp(testObj,'a/b/c/'));
console.log(getProp(testObj,'a/b/d/e'));
// invalid property paths
console.log(hasProp(testObj,'a/d/c/'));
console.log(hasProp(testObj,'a/b/d/e/f'));
console.log(getProp(testObj,'a/d/c/'));
console.log(getProp(testObj,'a/b/d/e/f'));
Upvotes: 1
Reputation: 731
you can do a recursive loop by checking against the object's properties. this thread describes how to check if that property exists.
How do I check if an object has a property in JavaScript?
and yes you will have to keep track as you recurse if you want to know the path.
Upvotes: 0