Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I maintain a variable in a recursive function?

I'm trying to get all the keys in an object, in a recursive way:

let keys = [];

const getAllKeys = (object) => {
  //Supposedly, `keys` should be here.
  Object.keys(object).some((k) => {
    if (object[k] && typeof object[k] === "object") {
      keys.push(k);
      getAllKeys(object[k]);
    }
  });
  return keys;
};

const obj = {
  key1: {
    key2: {
      key3: {}
    }
  }
};

getAllKeys(obj);

console.log(keys);

And as you can see, it works, but the problem is that keys is outside this function, but I need it to be inside so that I can just import this function and use it. I tried wrapping it in another, inner function, but it doesn't work.

What can I do here?

like image 215
Daniel James Avatar asked Sep 02 '26 14:09

Daniel James


2 Answers

You need a wrapper function to hold the accumulator:

const getAllKeys = (object) => {
    let keys = [];

    let getAllKeysInner = (o) => {
        Object.keys(o).forEach((k) => {
            keys.push(k);
            if (o[k] && typeof o[k] === "object") {
                getAllKeysInner(o[k]);
            }
        });
    }

    getAllKeysInner(object)
    return keys;
};

//

const obj = {
    key1: {
        key2: {
            key3: {}
        }
    }
};

console.log(getAllKeys(obj))

Note that your code needed a few fixes.

FWIW, this particular problem can be done simpler, without any accumulator at all:

const getAllKeys = (o) =>
    (o && typeof o === "object")
        ? Object.keys(o).flatMap(k => [k, ...getAllKeys(o[k])])
        : []

although the accumulator version might be more efficient since it doesn't allocate 1000s temporary arrays.

Finally, the VLAZ's idea to carry the accumulator around is a sane one, because is it as efficient as the wrapper version. It can be written concisely like this:

const getAllKeys = (o, keys=[]) =>
    o
    && typeof o === "object"
    && Object.keys(o).forEach(k => keys.push(k) && getAllKeys(o[k], keys))
    || keys
like image 118
georg Avatar answered Sep 04 '26 03:09

georg


You can convert keys to a parameter you pass recursively:

const getAllKeys = (object, keys = []) => {
  Object.keys(object).some((k) => {
    if (object[k] && typeof object[k] === "object") {
      keys.push(k);
      getAllKeys(object[k], keys);
    }
  });
  return keys;
};

const obj = {
  key1: {
    key2: {
      key3: {}
    }
  }
};

const result = getAllKeys(obj);

console.log(result);

This does mean that the first call might be getAllKeys(obj, ["some" "keys", "already", "present"]) or even something invalid like getAllKeys(obj, null). If you want to make sure that doesn't happen, convert your function to a recursion helper function and you can have a top level function that makes sure to call it correctly:

const getAllKeys = (object) => {
  const helper = (object, keys) => {
//      ^^^^^^ now an internal helper
    Object.keys(object).some((k) => {
      if (object[k] && typeof object[k] === "object") {
        keys.push(k);
        helper(object[k], keys);
//      ^^^^^^ recursively calls itself
      }
    });
    return keys;
  }
  
  //top level function just calls the helper:
  return helper(object, []);
};

const obj = {
  key1: {
    key2: {
      key3: {}
    }
  }
};

const result = getAllKeys(obj);

console.log(result);
like image 21
VLAZ Avatar answered Sep 04 '26 04:09

VLAZ



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!