Search code examples
javascripttraversal

Traverse all nodes and yield keys and value in Javascript


Given a JSON structure

var json = {
  'a': {
    'a1': ['a1a', 'a1b', 'a1c'],
    'a2': ['a2a', 'a2b'],
    'a3': []
  },
  'b': {
    'b1': ['b1a', 'b1b', 'b1c', 'b1d'],
    'b2': ['b2a', 'b2b'],
  },
  'c': ['c1', 'c2'],
  'd': {
    'd1': ['d1a', 'd1b', 'd1c'],
    'd2': []
  },
  'e': ['e1', 'e2'],
  'f': [
    'f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'f10'
  ]
};

Using Javascript how can I traverse this to yield an array of all keys and values? In other words, without simply keeping an external store and append to it as I traverse, how can I return an array of the entire set of node keys and values?

For example in Python I might make use of a generator. So perhaps I should ask, how can I use a generator in Javascript?

Javascript example with jQuery:

function traverse(jsonObj) {
  if( typeof jsonObj == "object" ) {
    $.each(jsonObj, function(k,v) {
      traverse(v);
    });
  }
  else {
    // how can I yield this value instead?
    console.log(jsonObj)
  }
}

Where my expected output would be an array of all keys and values:

['a', 'a1', 'a1a', 'a1b', 'a1c', 'a2', 'a2a', 'a2b'] // ... and so on

Solution

  • ECMAScript 6 has generators. So you can write the function as

    function *traverse(value) {
      if (typeof value === "object") {
        var isArray = Array.isArray(value);
        for (var prop in value) {
            if (!isArray) yield prop;
            yield* traverse(value[prop]);
        }
      }
      else {
        yield value;
      }
    }
    

    And use it as:

    var obj = {
      'a': {
        'a1': ['a1a', 'a1b', 'a1c'],
        'a2': ['a2a', 'a2b'],
        'a3': []
      }
    };
    
    console.log(Array.from(traverse(obj)));
    

    DEMO via 6to5


    But it's also not much different without native generators:

    function traverse(value, result) {
      result = result || [];
      if (typeof value === "object") {
        var isArray = Array.isArray(value);
        for (var prop in value) {
          if (!isArray) result.push(prop);
          traverse(value[prop], result);
        }
      }
      else {
        result.push(value);
      }
      return result;
    }
    
    console.log(traverse(obj));