# Get property value from object tree in a recursive way

Consider the following structure,

let condition = { and: [ { id: 3 }, { pageId: '1' } ] }


I want to get the id property value in a recursive way, it can be also nested inside other conditions.

for example the structure could be like that

let condition = {or: [{ and: [ { id: 3 }, { pageId: '1' } ] }, {age: 10}]}


I ended using this but I don't like the final syntax

  function getIdConditionValue(cond) {
if (isArray(cond.and)) {
return map(cond.and, getIdConditionValue);
} else if (isArray(cond.or)) {
return map(cond.or, getIdConditionValue);
}
if (cond.id) return cond.id;
}

const id = getIdConditionValue(condition).filter(x => x)[0];


It is working, but I would like to remove .filter(x => x)[0] part from the calling function, which I added because the returned value would be like this [1, undefined].

• It's a little hard to review code when there is missing code. If you could either, add some details about missing functions, or include the source That will help you get a good review. – Blindman67 Jan 8 '19 at 17:52
• Welcome to Code Review! Going along with what Blindman67 stated: Besides the obvious (i.e. getting a property value) What task does this code accomplish (the big picture)? Please tell us, and also make that the title of the question via edit. Maybe you missed the placeholder on the title element: "State the task that your code accomplishes. Make your title distinctive.". Also from How to Ask: "State what your code does in your title, not your main concerns about it.". – Sᴀᴍ Onᴇᴌᴀ Jan 8 '19 at 17:58
• Thanks for your kind words, I've edited the question as requested – Mustafa Magdy Jan 8 '19 at 18:52
• Why is recursion required? – guest271314 Jan 8 '19 at 18:53
• Are the two examples the only possible data structures? – guest271314 Jan 8 '19 at 18:58

Given the map() function used at the question having the same signature as jQuery.map() function used at the question, in order to avoid chaining .filter() to get the value at index 0 of the returned, you can use destructuring to assign id to the value at index 0 of the array returned from the function.

function getIdConditionValue(cond) {
if (Array.isArray(cond.and)) {
return $.map(cond.and, getIdConditionValue); } else if (Array.isArray(cond.or)) { return$.map(cond.or, getIdConditionValue);
}
if (cond.id) return cond.id;
}

var condition = {
or: [{
and: [{
id: 3
}, {
pageId: '1'
}]
}, {
age: 10
}]
}

const [id] = getIdConditionValue(condition);

console.log(id);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js">
</script>

For Loash and Underscore signatures of map() signature (each library's _.map() returns an array having two elements, the fist is the expected result, the second undefined) you can use

const [[id]] = getIdConditionValue(condition);


For an alternative recursive solution you can create a function that accepts an object and a property name for parameter, utilizes the recursive replacer function of JSON.stringify() and JSON.parse(). If the property name is found during recursive iteration of property names of the object passed, a parameter of an immediately invoked arrow function declared as undefined is set to the property value, else undefined is returned from the function.

const condition1 = {and: [{id:3}, {pageId:'1'}]};
const condition2 = {or: [{and: [{id: 3}, {pageId: '1'}]}, {age: 10 }]};
const condition3 = {and:[,,]};
const condition4 = {and:{or:[]}};
const condition5 = {or:[{or:[{id:1}]}]};

const getObjectPropertyValue = (o, prop) => (result =>
(JSON[typeof o === 'string' ? 'parse' : 'stringify'](o, (key, value) =>
key == prop ? (result = value) : value), result))(void 0);

let prop = 'id';

const id1 = getObjectPropertyValue(condition1, prop);

console.log(id1);

let condition2json = JSON.stringify(condition2);

const id2 = getObjectPropertyValue(condition2json, prop);

console.log(id2);

const id3 = getObjectPropertyValue(condition3, prop);

console.log(id3); // undefined

const id4 = getObjectPropertyValue(condition4, prop);

console.log(id4); // undefined

const id5 = getObjectPropertyValue(condition5, prop);

console.log(id5);

• You have a typo in the second call, think you mean condition2, also your function is prone to throwing errors. {and:[,,]}, {and:{or:[]}}, and {or:[{or:[{id:1}]}]} each throw a different error, and {and:[{id:false}],id:null} incorrectly returns false – Blindman67 Jan 8 '19 at 23:42
• @Blindman67 See updated post. – guest271314 Jan 9 '19 at 0:15
• @Blindman67 What is the expected result of {and:[{id:false}],id:null}? The language at the question is "the id property value". – guest271314 Jan 9 '19 at 0:55
• I am guessing its undefined due to the line if (cond.id) return cond.id; from OP code However its is completely unclear what the actual needs are as the OP's code only prioritises and above or and gives no indication that and and or are defining logic. Eg and means all ids in array are truthy and or means any id is truthy. Unless the call to map has something going on in it (don't think so). At face values its a search for the FIRST truthy id along prop paths with and having priority, then or and last id. – Blindman67 Jan 9 '19 at 1:40
• @MustafaMagdy (result => (JSON[typeof o === 'string' ? 'parse' : 'stringify'](o, (key, value) => key == prop ? (result = value) : value), result))(void 0) is an immediately invoked arrow function. The purpose is to 1) define the variable result as undefined within the function without declaring the variable as a parameter of getObjectPropertyValue 2) return result either being set to value if key == prop or remain undefined. The call to JSON[stringify/parse] is wrapped in parenthesis to allow the comma operator which follows ((..), result) where result is always returned. – guest271314 Jan 9 '19 at 19:34

If you just want the first result of any 'id' property in the structure you can just do a recursive search:

let condition = {or: [{ and: [ { id: 3 }, { pageId: '1' } ] }, {age: 10}]}

function getFirstId(struct){
for(let prop in struct){
let val = struct[prop];
if(prop === 'id') return val;
if("object" === typeof val) return getFirstId(val);
}
}

console.log(getFirstId(condition));

• Your function is not safe when object or arrays contain cyclic references (throws error), returns the wrong value for {id:false}, unsafely uses for in and will recurse on a null property (eg typeof null === "object" is true) – Blindman67 Jan 8 '19 at 23:54
• true. other answers made assumptions about the structure of the input so I did as well. maybe I'll update it later. – I wrestled a bear once. Jan 9 '19 at 14:47