Ensure nested objects/hashes exist

Recently I was working on someone else's code and found quite a lot of repetition of similar code in their JS files. Here's an example:

if(typeof(flamethrower) === 'undefined'){ flamethrower = {} };
if(typeof(flamethrower.fuel) === 'undefined'){ flamethrower.fuel = {} };
if(typeof(flamethrower.fuel.fire) === 'undefined'){ flamethrower.fuel.fire = {} };

This sort of pattern kept coming up in file after file.

There are 2 things to learn about the project from this code. The first is that there is obviously a loading problem.

The number of files that followed this pattern was a serious indicator that someone either wasn't investigating a load order problem, or that the developer was being overly protective of the code in a particular file to avoid errors. So the first step when encountering this is to investigate dependencies and load order.

But sometimes you may actually need to ensure that some nested structure exists. Your load order may need to be necessarily arbitrary or unpredictable.

To DRY up this code we can write a recursive function and get something as simple as:

app.ensure_struct('flamethrower.fuel.fire')

This makes our code much easier to process and understand. In the first example you see so much code that you are required to slow down and read carefully to ensure you haven't missed anything. A simple typographical error would be very easy to miss and could introduce an unexpected bug.

How can recursion help us here? Let's take a look at the final function first:

var app = {}

app.ensure_struct = function(atts,scope){
  var scope = scope || this;
  if (atts == "") {
      return scope;
  }
  var parts = atts.split('.'),
      first_attribute = parts.shift();

  if (typeof (scope[first_attribute]) === 'undefined') {
      scope[first_attribute] = {};
  }
  app.ensure_struct(parts.join('.'), scope[first_attribute]);
  return scope;
}

We've defined this on an app object but that can be whatever you want. First, what we care about is some string representing the structure that we need. In our example we used "flamethrower.fuel.fire" so that's the first argument that we need to handle.

app.ensure_struct = function(atts){
  var parts = atts.split('.');
}

This gives us an array of parts to turn into objects. We'll also need a starter object to add these items and we can begin to check if they exist or set them.

app.ensure_struct = function(atts){
  var parts = atts.split('.');
  var starter_object = {};

  if(typeof(starter_object[parts[0]]) === 'undefined'){
    starter_object[parts[0]] = {};
  }
}

The problem here is that we have begun by getting the first of the attributes from our array. We still need to address the rest of our attributes. One way to do this would be to loop over the array of parts and add those named objects to the last object we had in hand:

var last_object = starter_object;
var index = 0;
while (index < parts.length) {
  if (typeof (last_object[parts[index]]) === 'undefined') {
    last_object[parts[index]] = {};
  }
  last_object = starter_object[parts[index]];
  index++;
}

You could instead use a for loop to shrink it a bit:

for(var i = 0, o = starter_object, l = parts.length; i < l; i++){
    attribute = parts[i]
    if(typeof(o[attribute]) === 'undefined'){
       o[attribute] = {};
   }
   o = o[attribute];
}

As an alternative, we can use recursion to tackle our needs. This helps simplify the code a bit:

var parts = atts.split('.'),
    first_attribute = parts.shift();

if (typeof (scope[first_attribute]) === 'undefined') {
    scope[first_attribute] = {};
}
app.ensure_struct(parts.join('.'), scope[first_attribute]);

I'm happier with that. Inside our ensure_struct function we reuse what we've already written by joining our leftovers and sending a new to the fuction. Of course, to get this to work we need to change the arguments that the function accepts and check that we haven't sent a blank string in for the attributes. When we hit our last part, the leftover_attributes is an empty array and joining it will return a blank string.

app.ensure_struct = function(atts,scope){
  var scope = scope || this;
  if (atts == "") {
      return scope;
  }
  var parts = atts.split('.'),
      first_attribute = parts.shift();

  if (typeof (scope[first_attribute]) === 'undefined') {
      scope[first_attribute] = {};
  }
  app.ensure_struct(parts.join('.'), scope[first_attribute]);
  return scope;
}

Now, we can set our scope from the outside if we like.

app.ensure_struct('values.one.two', other_object);

And yes, this comes from an actual project named flamethrower.