HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptMinor

Converting an object to be used with a treegrid

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
treegridwithusedconvertingobject

Problem

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?

JSBin

```
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 gr

Solution

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          = '

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. + earned.toFixed(2); leaf.spent = '

Addendum

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

%%CODEBLOCK_1%%

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. + 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 '

Addendum

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

%%CODEBLOCK_1%%

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. + ((e - (cl * s)) / cl).toFixed(3); } function netEarned(e, s) { if( !e ) return '$0.00'; return '

Addendum

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

%%CODEBLOCK_1%%

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. + (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

%%CODEBLOCK_1%%

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.

Code Snippets

// 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) + '%';
}
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;
}

Context

StackExchange Code Review Q#16136, answer score: 2

Revisions (0)

No revisions yet.