bx ded
bx ded

Reputation: 49

Returning array of nested keys recursively javascript

Trying to create a function that returns an array of the keys of an object, including of objects nested within. My code looks like this:

function keyCount(obj, arr) {
  if (arr == undefined){
    arr = []
  }
  let keys = Object.keys(obj);
  let length = keys.length;
  for (i = 0; i <= length; i++){
    if (Object.keys(obj)[i] !== undefined){
      arr.push(Object.keys(obj)[i]);
    }
  }
  for (i = 0; i <= length; i++){
    if (typeof(obj[Object.keys(obj)[i]]) === "object" && obj[Object.keys(obj)[i]] !== null && Object.keys(obj)[i] !== undefined){
      let nestedobj = obj[Object.keys(obj)[i]];
      keyCount(nestedobj,arr)
    }
  }
  return (arr);
}

This returns the first level keys and the keys of one nested object, but exits the function after the first recursive call. Is there a way around this or a better way to format the code? Thanks in advance!

Edited: Data expectations:

let obj1 = {
1:"a",
2:{
  3: "b",
  4: "c"},
5:{
  6:"d",
  7: "e",
  8: {
    9: "f",
    10: "g"},
    11:{
      12:"h",
      13: "i"
      }
    }
  };

Should return:

[1,2,5,3,4,6,7,8,9,10,11,12,13]

But only returns:

[1,2,5,3,4]

Upvotes: 0

Views: 233

Answers (4)

georg
georg

Reputation: 214949

Since the answers posted so far don't handle circular objects, here's the one that does:

function getKeys(obj) {
    let seen = new WeakSet(),
        keys = new Set(),
        queue = [obj];

    while (queue.length) {
        let obj = queue.shift();
        
        if (!obj || typeof obj !== 'object' || seen.has(obj))
            continue;
        
        seen.add(obj);
        
        for (let [k, v] of Object.entries(obj)) {
            keys.add(k);
            queue.push(v);
        }
    }

    return [...keys];
}

//

weirdo = {a: 1, b: 2, c: 3}
weirdo.self = weirdo;
console.log(getKeys(weirdo))

And it's not recursive, so it handles more than 10,000 levels of nesting (which is a quite common situation in today's webdev ;)

Upvotes: 0

Doug
Doug

Reputation: 1515

let obj1 = {
1:"a",
2:{
  3: "b",
  4: "c"},
5:{
  6:"d",
  7: "e",
  8: {
    9: "f",
    10: "g"},
    11:{
      12:"h",
      13: "i"
      }
    }
  };

var keyArray = [];

function getKeys(obj){
  // get the keys from the current level
  var keys = Object.keys( obj );
  // iterate through each key, add it to our keyArray[]
  for( var i=0, x=keys.length; i<x; i++ ){
    keyArray.push( keys[i] );
    // if there is another array/object (instead of a
    // value) send that section of the obj back into
    // the function
    if( obj[keys[i]].constructor === Object ){
      getKeys( obj[keys[i]] );
    }
  }
}

getKeys(obj1);

//Should return:
//console.log( 'expected', [1,2,5,3,4,6,7,8,9,10,11,12,13] );
console.log( 'result: ', keyArray.join(',') );

Upvotes: 0

Bergi
Bergi

Reputation: 664196

There are various problems with your code. The most important is that i is implicitly global, which causes your recursive call to alter the loop condition. You're lucky that you didn't get an infinite loop…

function keyCount(obj, arr) {
  "use strict";
//^^^^^^^^^^^^^ prevent mistakes with forgotten declarations
  if (arr == undefined){
    arr = []
  }
  const keys = Object.keys(obj);
//^^^^^ prefer const over let when you don't assign
  const length = keys.length;
  for (let i = 0; i < length; i++) {
//                 ^^^ don't use <=
//     ^^^ i needs to be a local variable!
    // if (keys[i] !== undefined)
//  ^^^ no longer necessary when having fixed the loop
      arr.push(keys[i]);
//             ^^^^ don't call Object.keys again
  }
  for (let i = 0; i < length; i++) {
//     ^^^          ^ same as above
    const nestedobj = obj[keys[i]]; // move this up here
//                        ^^^^ same as above
    if (typeof nestedobj === "object" && nestedobj !== null  ) {
//            ^         ^ typeof is not a function          ^ same as above
      keyCount(nestedobj, arr);
    }
  }
  return arr;
}

Upvotes: 0

Nina Scholz
Nina Scholz

Reputation: 386520

You could take the keys and check if the value is an object, then take this keys as well.

function getKeys(object) {
    return Object
        .keys(object)
        .reduce((r, k) => r.concat(k, object[k] && typeof object[k] === 'object' ? getKeys(object[k]) : []), []);
}


var object = { 1: "a", 2: { 3: "b", 4: "c" }, 5: { 6: "d", 7: "e", 8: { 9: "f", 10: "g" }, 11: { 12: "h", 13: "i" } } };

console.log(getKeys(object));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Upvotes: 1

Related Questions