Code Review Stack Exchange is a question and answer site for peer programmer code reviews. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

I'm a bit novice/moderate at JS. To my surprise I managed to put this together and it does work for what I intended.

The objective is to convert an object into a format that is more conducive to being used with a treegrid. I'm using the underscore library to grab object keys at various points.

I just wanted to know if maybe there's a better or perhaps more performant way of looping through these deeply nested objects that I may be overlooking. Something that's capable of handling hundreds, or even a thousand, of these nodes without potentially locking up a browser?

I found a hint about storing the length variables separately in a similar question that displayed looping through things in this manner, I just wanted to know if there may be any other tips?

var data_source = {"sprint":{"children":{"country":{"us":{"clicks":26}},"device":{"iphone":{"clicks":26}},"isp":{"comcast cable":{"clicks":26}},"os":{"ios":{"clicks":15},"android":{"clicks":10}},"referer":{"unknown":{"clicks":26}}},"clicks":26},"verizon":{"children":{"country":{"us":{"clicks":10,"conversions":5,"earned":0.5,"spent":0.2}},"device":{"galaxy s":{"clicks":1}},"isp":{"comcast cable":{"clicks":1}},"os":{"android":{"clicks":1}},"referer":{"unknown":{"clicks":1}}},"clicks":1,"conversions":1}};

var object = [];

function convert(data){
  var keys = _.keys(data);
  var keyslength = keys.length;
  for (var i = 0; i < keyslength; i++){
    var me = data[keys[i]];
    var rootleaf = buildLeaf(me);
    rootleaf.name = keys[i];
    rootleaf.children = [];

    var childkeys = _.keys(me.children);
    var childkeyslength = childkeys.length;
    for (var i2 = 0; i2 < childkeyslength; i2++){
      var childme = me.children[childkeys[i2]];
      var childleaf = {};
      childleaf.name = childkeys[i2];
      childleaf.children = [];

      var grandchildkeys = _.keys(childme);
      var grandchildkeyslength = grandchildkeys.length;
      for (var i3 = 0; i3 < grandchildkeyslength; i3++){
        var grandchildme = childme[grandchildkeys[i3]];
        var grandchildleaf = buildLeaf(grandchildme);
        grandchildleaf.name = grandchildkeys[i3];
        childleaf.children.push(grandchildleaf);      
      }

      rootleaf.children.push(childleaf);     
    }

    object.push(rootleaf);    
  }
}

convert(data_source);
console.log(object);

function buildLeaf(node){
  var tempnode = {};
  var clicks = ((node.clicks) ? node.clicks : 0);
  var conversions = ((node.conversions) ? node.conversions : 0);
  var earned = ((node.earned) ? node.earned : 0);
  var spent = ((node.spent) ? node.spent : 0);
  tempnode.clicks = clicks;
  tempnode.conversions = conversions;
  tempnode.earned = '$' + earned.toFixed(2);
  tempnode.spent = '$' + spent.toFixed(2);
  tempnode.conversion_rate = conversionRate(conversions,clicks);
  tempnode.net_earned = netEarned(earned,spent);
  tempnode.epc = epc(clicks,earned,spent);
  tempnode.roi = roi(earned,spent);
  return tempnode;
}


// calculation functions

function conversionRate(cv, cl) {
    if (cl === 0) return '0%';
    return ((cv/cl)*100).toFixed(1) + '%';
}
function epc(cl, e, s) {
    if (cl === 0 || e === 0) return '$0.000';
    return '$' + ((e - (cl * s)) / cl).toFixed(3);
}
function netEarned(e, s) {
    if (e === 0) return '$0.00';
    return '$' + (e - s).toFixed(2);
}
function roi(e, s) {
    if (e === 0) return '0%';
    return (((e - s) / s) * 100).toFixed(0) + '%';
}

Here's a jsbin link for posterity: http://jsbin.com/uwudaw/3/edit

Thanks for any advice.

share|improve this question
up vote 1 down vote accepted

I believe this does the same as yours, except it uses buildLeaf on each step (your function skips it in the "middle" loop, and just makes an empty object).

// Recursive convertion function
function convert(obj) {
  var item, leaf, key, leaves = [];
  for( key in obj ) {
    if( !obj.hasOwnProperty(key) ) continue;
    item = obj[key];
    leaf = buildLeaf(key, item)
    leaves.push(leaf);
    if( typeof item.children === 'object' ) {
      leaf.children = convert(item.children);
    }
  }
  return leaves;
}

// I also refactored buildLeaf slightly to accept both
// name and content, and to make use of JavaScript's
// `||`-style fallbacks

function buildLeaf(name, node) {
  var leaf        = { name: name, children: [] },
      clicks      = node.clicks || 0,
      conversions = node.conversions || 0,
      earned      = node.earned || 0,
      spent       = node.spent || 0;

  leaf.clicks          = clicks;
  leaf.conversions     = conversions;
  leaf.earned          = '$' + earned.toFixed(2);
  leaf.spent           = '$' + spent.toFixed(2);
  leaf.conversion_rate = conversionRate(conversions,clicks);
  leaf.net_earned      = netEarned(earned,spent);
  leaf.epc             = epc(clicks,earned,spent);
  leaf.roi             = roi(earned,spent);

  return leaf;
}

// In the calculation functions, I made the value checking
// less strict. Generally, strong checking is encouraged,
// but in this case other useless values would slip through,
// when only strongly checking for zero (since that allows
// `false`, empty strings, etc. to pass through).

function conversionRate(cv, cl) {
  if( !cl ) return '0%';
  return ((cv/cl)*100).toFixed(1) + '%';
}

function epc(cl, e, s) {
  if( !cl || !e ) return '$0.000';
  return '$' + ((e - (cl * s)) / cl).toFixed(3);
}

function netEarned(e, s) {
  if( !e ) return '$0.00';
  return '$' + (e - s).toFixed(2);
}

function roi(e, s) {
  if( !e ) return '0%';
  return (((e - s) / s) * 100).toFixed(0) + '%';
}

Addendum

If you need to handle the conversion differently for different levels of nesting (re: the comments), you could do something like this

function convert(obj) {
  // get the extra, optional, argument.
  // It defaults to 1, and increments otherwise
  var level = (arguments[1] || 0) + 1;

  var item, leaf, key, leaves = [];
  for( key in obj ) {
    if( !obj.hasOwnProperty(key) ) continue;
    item = obj[key];
    // Check the level here
    if( level === 2 ) {
      // 2nd level will only be converted to a simpler, non-leaf object
      leaf = { name: key, children: [] };
    } else {
      // Other levels will become "leaves"
      leaf = buildLeaf(key, item);
    }
    leaves.push(leaf);
    if( typeof item.children === 'object' ) {
      // remember to pass the level on
      leaf.children = convert(item.children, level);
    }
  }
  return leaves;
}

I don't know if there's a system in the data you're converting, but you could also check for level % 2 === 0 to handle every second (i.e. even-numbered) level differently.

share|improve this answer
    
Hey thanks for this! There's some good stuff here to take away. The only thing is that the blank 'middle' object is intentional as it's just to serve as a 'folder' row that has no values. I'm just trying to figure out how to break in to the function to determine when it hits the middle bit so that I can return a leaf with only a name for it. – Ataraxy Oct 3 '12 at 21:01
    
@Ataraxy Well, I figured there was a reason for the way you were doing it, but I also figured that the extra values in the "middle leaves" would do no harm by being there. But if it's a problem, you could for instance use a counter; increment it for each "level", and check what level you're on before making a leaf. I'll add some code for that. – Flambino Oct 3 '12 at 21:24
    
This is awesome, thanks! :) – Ataraxy Oct 3 '12 at 22:18

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.