0

Welcome,

Question about correct answer from topic: Get all keys of a deep object in Javascript

I don't understand how this "getDeepKeys" function works. I know that there are a lot of threads related to this topic, but they don't give me the full answer to my question.

The details of my question are:

  1. piece of code on which I am testing the function:

var data2 = {
  OrderMessage:{
    EntityId: "ZZ-KR/00000002/2020/1",
    Buyer1: {
      GLN: "GLN.6340134346_Buyer_1",
    },
    Seller1: {
      GLN: "GLN.6340134346_Seller_1",
    },
  },
  OrderResponseMessage: {
    Standard: "STANDARD_NAME",
    Buyer2: {
      GLN: "GLN.6340134346"
    },
    Seller2: {
       GLN: "GLN.7690933887",
    },
  }
}
function getDeepKeys(obj) {
  var keys = [];
  for(var key in obj) {
      keys.push(key);
      if(typeof obj[key] === "object") {
          var subkeys = getDeepKeys(obj[key]);
          keys = keys.concat(subkeys.map(function(subkey) {
              return key + "." + subkey; 
          }));
      }
  }
  return keys;
}

var objectt = getDeepKeys(data2);
console.log(objectt);
  1. at the beginning the keys array is declared = []
  2. During the first iteration, a value is added to this array and if the value is an object, the getDeepKeys(obj[key]) function is called again in var subkeys; with another value.
  3. this is the first iteration and in the "keys" array I have stored the value from my "OrderMessage" object Declaring keys - first iteration
  4. then after calling the function, an empty key array is created again keys = [] Declaring keys - second iteration
  5. I don't understand at this point how this data is stored if the arra that was supposed to store it is declared in the memory again and yet the function at the very end returns correct results?

Please help me to explain.

Md Sabbir Alam
  • 4,937
  • 3
  • 15
  • 30
Wamboo
  • 3
  • 3
  • Just a side note: That `getDeepKeys` isn't particularly efficient, it creates a lot of objects and copies things around a lot unnecessarily. (Even if doing FP, at least you'd want to pass the prefix to the inner calls...) It probably doesn't matter for getting all the keys from an object tree (there probably won't be that many), but I wouldn't generalize the pattern to something much larger, like getting a list of all files in a directory structure recursively, without paying attention to whether it impacted the peformance and being ready to deal with it if it did. – T.J. Crowder Jan 04 '21 at 09:32
  • The array that is created for a consecutive function call is not the same at the one created for the previous call. Visualizing how the code is executed might help you: http://www.pythontutor.com/javascript.html – marzelin Jan 04 '21 at 09:35
  • Each time `getDeepKeys` is called, the function obtains the keys at certain depth in an object. These keys need to be stored in a temporary place and returned back to the caller. That's the purpose of `keys`: it stores the work of that call. It is returned and the caller will merge with its own `keys`, or if it is the first call, it will just return that array. – Edgar G. Jan 04 '21 at 09:39
  • @marzelin that is one nice visualizer. Loved it :) – Md Sabbir Alam Jan 04 '21 at 09:41

3 Answers3

1

From your question my understanding is that you are confused how the different iteration of the getDeepKeys() method knows to which keys variable it should save the value.

then after calling the function, an empty key array is created again keys

Yes it creates another variable named keys. But unlike we human computer identify each keys by their address in the memory. You can declare as many variable with same name as you want in different iteration of the method. They will be stored in different memory location in your PC. That is why the getDeepKeys() method never confuses to which keys it should save the array.

Lets say for the first iteration when you create the keys array it is being stored in the memory location 10AD32 and for the 2nd iteration when you create another keys array it will be stored in a different memory location such as 11CD34. Therefore although both variable have name keys but computer can identify them differently.

So for each iteration you will have a different keys variable.

For better understanding you can read this, this and this.

Note that when describing the array storing procedure I intentionally over simplified it for you. The whole array will not stored in a single memory location. In reality the array name refers to the position where the array head is stored.

Md Sabbir Alam
  • 4,937
  • 3
  • 15
  • 30
0

I don't understand at this point how this data is stored if the arra that was supposed to store it is declared in the memory again and yet the function at the very end returns correct results?

That's handled by the concat call:

var subkeys = getDeepKeys(obj[key]);
keys = keys.concat(subkeys.map(function(subkey) {
    return key + "." + subkey; 
}));

That call:

  1. Uses getDeepKeys to get the array of keys for the inner call.
  2. Uses map to add key + "." to the beginning of each key.
  3. Uses concat to get a new array with everything from keys plus everything from the array from #2 above, assigning that array to keys.

So at each level of recursive call, the results from the inner recursive call are added to the results for the "current" recursive call via that concat.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
0

The other answers here explain the provided code, but the root of the problem (your confusion) is the code itself. Recursion is a functional heritage and so using it with functional style yields the best results. This means avoiding things like mutation, variable reassignments, and other side effects. Consider this implementation of deepKeys that is written using a single expression -

const deepKeys = (t, keys = []) =>
  Object(t) === t
    ? Object
        .entries(t)
        .flatMap(([k,v]) => deepKeys(v, [...keys, k]))
    : [ keys.join(".") ]
    
const input =
  {OrderMessage:{EntityId: "ZZ-KR/00000002/2020/1",Buyer1:{GLN: "GLN.6340134346_Buyer_1"},Seller1:{GLN: "GLN.6340134346_Seller_1"}},OrderResponseMessage:{Standard: "STANDARD_NAME",Buyer2:{GLN: "GLN.6340134346"},Seller2:{GLN: "GLN.7690933887"}}}

for (const path of deepKeys(input))
  console.log(path)

Another great choice to implement this program is JavaScript's generators. Notice the similarity between this deepKeys and the implementation above. They both effectively do the same thing -

function* deepKeys (t, keys = [])
{ switch(t?.constructor)
  { case Object:
      for (const [k,v] of Object.entries(t))
        yield* deepKeys(v, [...keys, k])
      break
    default:
      yield keys.join(".")
  }
}

const input =
  {OrderMessage:{EntityId: "ZZ-KR/00000002/2020/1",Buyer1:{GLN: "GLN.6340134346_Buyer_1"},Seller1:{GLN: "GLN.6340134346_Seller_1"}},OrderResponseMessage:{Standard: "STANDARD_NAME",Buyer2:{GLN: "GLN.6340134346"},Seller2:{GLN: "GLN.7690933887"}}}

for (const path of deepKeys(input))
  console.log(path)

Output is the same for each variant of deepKeys -

OrderMessage.EntityId
OrderMessage.Buyer1.GLN
OrderMessage.Seller1.GLN
OrderResponseMessage.Standard
OrderResponseMessage.Buyer2.GLN
OrderResponseMessage.Seller2.GLN
Mulan
  • 129,518
  • 31
  • 228
  • 259