easierObject - Micro-library to Simplify Read/Write Operations at Random Locations in Javascript Object Trees

The Problem

One of my pet peeves with working with Javascript objects is that there is no native way to read from or write to an arbitrary location in a Javascript object tree without first checking whether each internal node in the tree exists. It is easy to operate on a single object, but working with a tree necessitates boilerplate work. What do I mean by this?

Say an object "data" exists that represents the root node of a tree of data. Assume this tree is already pre-populated from a web service API call. The current user's name needs written to a specific location in the tree, then the data is passed on to another function that shows the data on the screen.

This would be easy if the user's name could be set at "data.username", but the template is expecting user related data to live under the "data.user" namespace, with the name being at "data.user.name".

At first thought, this should be easy, write:

data.user = { name: "Billy Jim Bob" };

But what if data.user already existed and contained a field "userid"? The user's userid is now lost. Uh oh. So we modify this to:

// data initially contains data.user.userid = 1;
// create new user object only if needed (in this case it is not)
data.user = data.user || {};

// do the interesting work.
data.user.name = "Billy Jim Bob";

// data.user now contains two fields, name, userid.

Fair enough, not too much code. But what if this was taken a step further, what if name was further split into first, middle, and last, all under the data.user.name node?

// Boilerplate for each internal node to create the node
// only if needed.
data.user = data.user || {};
data.user.name = data.user.name || {};

// Actual work on the leaf nodes
data.user.name.first = "Billy";
data.user.name.middle = "Jim";
data.user.name.last = "Bob";

That two lines of boilerplate shouldn't be necessary.

To read from an arbitrary location in a tree requires a different sort of boilerplate - a try/catch to avoid a TypeError when trying to read an item from an undefined object.

// data.user.address may or may not exist at this point.

try {
   var streetAddress = data.user.address.street;
catch(e) {
   // try/catch is used to avoid a TypeError if 
   // address is not defined.

Why can't this be easy?

The Solution?

I am working on two micro-libraries to simplify this, but I am not sure if I have an optimal interface yet. The first, called easierObject, is to work with normal Javascript objects. The second is for working with localStorage and is called easierStorage.

The APIs are modeled after localStorage, using setItem, getItem and removeItem.

// Rewrite of full name example
data = new easierObject(data);
user.setItem("user", "name", "first", "Billy");
user.setItem("user", "name", "middle", "Jim");
user.setItem("user", "name", "last", "Bob");

Only one line of boilerplate, but at the same time, this seems just as verbose.

Reading an arbitrary location is very similar:

var firstName = data.getItem("user", "name", "first");

A Better API?

Instead of separating the name of each node, another possible approach is to have a single name field with each node name separated by a period - in keeping with normal Javascript notation.

data.setItem("user.name.first", "Billy");
data.setItem("user.name.middle", "Jim");
data.setItem("user.name.last", "Bob");

var firstName = data.getItem("user.name.first");

This seems a far easier to grok, but it prevents users from having node names that contain periods. Is this an acceptable restriction? What if there was a special sequence "." that specified "this is not a separator"?


So obviously it is still a work in progress.

The code lives on GitHub at https://github.com/stomlinson/easierObject.

Any thoughts on a good way to go?