Andrei Rosu
Andrei Rosu

Reputation: 1387

Looping multilevel Object

Assuming we have the following Object, what would be the best way to iterate it up to it's end in order to get the name property for each Object? Please notice, that the size of the Object may vary and the browsing should be done in this order: a, b, a1, a2, b1, a21, b11, b12 ...

var obj = {
  a: {
    name: 'a',
    a1: {
      name: 'a1'
    },
    a2: {
      name: 'a2',
      a21: {
        name: 'a21'
      }
    }
  },

  b: {
    name: 'b'
    b1: {
      name: 'b1',
      b11: {
        name: 'b11'
      },
      b12: {
        name: 'b12'
      }
    }
  }
};

Upvotes: 0

Views: 482

Answers (5)

le_m
le_m

Reputation: 20228

You are looking for breadth-first traversal:

// Breadh-first object traversal:
function traverse(...objs) {
  for (let obj of objs) {
    let next = Object.values(obj).filter(val => val && typeof val === 'object');
    objs.push(...next);
  }
  return objs;
}

// Example:
let obj = {
  a:{name:"a",a1:{name:"a1"},a2:{name:"a2",a21:{name:"a21"}}},
  b:{name:"b",b1:{name:"b1",b11:{name:"b11"},b12:{name:"b12"}}}
};

for (let child of traverse(obj)) {
  if (child.name) console.log(child.name);
}

Upvotes: 0

Mulan
Mulan

Reputation: 135197

Here's a simple recursive function to get all the name properties in breadth-first order. I'm using a helper, pairs, that makes it easier to process the key-value pairs provided by each object. From there, it's a simple case analysis for how the recursive function should respond:

  • base case – return the accumulator
  • key is name, append value to the accumulator
  • value is an object, add pairs of value to the list of pairs to process
  • default case - do nothing and process the next pair

This answer differs from others in that there is no side effect from running it. Instead of hard coding some behavior in the loop function itself, loop returns an array of name property values that you can then do with whatever you wish.

const pairs = o =>
  Object.keys(o).map(k => ({key: k, value: o[k]}))
  
const loop = o => {
  const aux = (acc, [x,...xs]) => {
    if (x === undefined)
      return acc
    else if (x.key === 'name')
      return aux([...acc, x.value], xs)
    else if (Object(x.value) === x.value)
      return aux(acc, xs.concat(pairs(x.value)))
    else
      return aux(acc, xs)
  }
  return aux([], pairs(o))
}

const obj = { a: { name: 'a', a1: { name: 'a1' }, a2: { name: 'a2', a21: { name: 'a21' } } }, b: { name: 'b', b1: { name: 'b1', b11: { name: 'b11' }, b12: { name: 'b12' } } } }
  
console.log(loop(obj))
// [ 'a', 'b', 'a1', 'a2', 'b1', 'a21', 'b11', 'b12' ]

Alternatively, you could implement loop using a generator such that you could act on the values while iterating. Let me know if this interests you and I'll do a write up.


Edit

Original answer processed the object in the incorrect order. The above code now answers the question properly ^_^

Upvotes: 0

Pankaj Shukla
Pankaj Shukla

Reputation: 2672

What you are looking for is a breadth-first solution which Nina has rightly mentioned. Here is my implementation of it. In this solution, you can store the result in the array and then do console.log later.

var obj = {
  a: {
    name: 'a',
    a1: {
      name: 'a1'
    },
    a2: {
      name: 'a2',
      a21: {
        name: 'a21'
      }
    }
  },
  b: {
    name: 'b',
    b1: {
      name: 'b1',
      b11: {
        name: 'b11'
      },
      b12: {
        name: 'b12'
      }
    }
  }
};

var ans = [];
var q = [];
q.push(obj);

function getAllKeys() {
  if (q.length == 0) {
    return;
  }
  var obj = q.shift();

  var keys = Object.keys(obj);
  ans = ans.concat(keys);
  var index = ans.indexOf('name');
  if (index != -1) {
    ans.splice(index, 1);
  }

  for (var i = 0; i < keys.length; i++) {
    if (typeof obj[keys[i]] == 'object') {
      q.push(obj[keys[i]]);
    }

  }
  getAllKeys();
}
getAllKeys();
console.log(ans);

Upvotes: 1

Nina Scholz
Nina Scholz

Reputation: 386520

You could use a breadth-first search. It is an algorithm which is iterating every level of the tree first and then the next level.

This implementation works with a queue of nodes, that means, to call the function breadthFirst, the object/single node must be wrapped in an array.

function breadthFirst(queue) {
    var newQueue = [];
    queue.forEach(function (node) {
        ('name' in node) && console.log(node.name);
        Object.keys(node).forEach(function (k) {
            node[k] && typeof node[k] === 'object' && newQueue.push(node[k]);
        });
    });
    newQueue.length && breadthFirst(newQueue);
}

var object = { a: { name: 'a', a1: { name: 'a1' }, a2: { name: 'a2', a21: { name: 'a21' } } }, b: { name: 'b', b1: { name: 'b1', b11: { name: 'b11' }, b12: { name: 'b12' } } } };

breadthFirst([object]); // a b a1 a2 b1 a21 b11 b12
.as-console-wrapper { max-height: 100% !important; top: 0; }

Upvotes: 2

user7024559
user7024559

Reputation:

You need a recursive thing here. You are free to change the console.log to push somewhere or whatever...

var obj = {
    a: {
        name: 'a',
        a1: {
            name: 'a1'
        },
        a2: {
            name: 'a2',
            a21: {
                name: 'a21'
            }
        }
    },
        b: {
        name: 'b',
        b1: {
            name: 'b1',
            b11: {
                name: 'b11'
            },
            b12: {
                name: 'b12'
            }
        }
    }
};

var looping = function(obj) {
    var keys = Object.keys(obj);
    for (var i = 0; i < keys.length; i++) {
        if(typeof obj[keys[i]] === 'string') console.log(obj[keys[i]]);
        else looping(obj[keys[i]]);
    }
}

looping(obj);

Upvotes: 0

Related Questions