Object literals in JavaScript are both a blessing and a curse. In statically typed languages like C# or Java, objects have defined APIs which cannot be modified after compilation. Because JavaScript is a dynamically typed language, objects have no such restriction. I may add or remove properties and methods as I see fit during runtime. This gives me flexibility in that I can add or remove information as needed, but it also makes it very difficult to determine what the “shape” of an object should be at any point in time. This is especially true across API boundaries.

Recently I worked on a project where messages were delivered to application modules over a service bus. Each message had a sort of informal envelope/data schema that the module was expected to understand, unravel, and then pass the actual “data” part of the message to other application modules as necessary. The shape of this data was often arbitrary, or at best shaped according to undocumented conventions, so I frequently saw these kinds of test conditions:

function handleData(data) { if (data && data.customer && data.customer.accounts && data.customer.accounts.length) { // do something with the accounts } }

Or the more clever cousin:

function handleData(data) { // `accounts` will only be set if all // conditions eval to true var accounts = data && data.customer && data.customer.accounts; if (accounts) { // do something with the accounts } }

Dealing with literals

I had little control over the data being created, but I wanted a better means to test for, or access, properties on these object literal messages. Necessity, being the mother of invention, drove me to create the JavaScript library l33teral, a suite of methods that wrap an object literal to make working with its structure a less cumbersome experience. l33teral may be used in both node.js and desktop browser environments. For the examples above, I was able to reduce the number of test conditions to a single statement. (Assume leet represents the imported l33teral module.)

var leet = require('l33teral'); // or `var leet = window.l33teral` // or require(['l33teral'], function (leet) {...}); function handleData(data) { if (leet(data).probe('customer.accounts')) { // do something with the accounts } }

I could also avoid test conditions altogether by specifying default values for literal paths that did not exist.

function handleData(data) { var accounts = leet(data).tap('customer.accounts', []); accounts.forEach(function (a) { // do something with the accounts }); }

Sometimes I found it necessary to modify a large object graph at a specific path but since I could not be sure of the literal’s structure at runtime, I needed to set up the graph if the given path did not exist.

function addSchedule(customer, days) { customer.department = customer.department || {}; customer.department.schedule = customer.department.schedule || {}; customer.department.schedule.days = days; return customer; }

With l33teral this becomes trivial because the plant method will recursively create the segments in a path you specify, if any do not exist.

function addSchedule(customer, days) { leet(customer).plant('department.schedule.days', days); return customer; }

l33teral has other utility methods as well, such as hasAllProperties() and hasAnyProperties() , which invoke an object’s hasOwnProperty() method for a varying number of arguments, returning true if all all present (in the case of the former) or if any are present (in the case of the latter).

You can pull data from various paths on an object literal into an array with collect() , or use extract() to load them into another object literal where the keys are the string representations of the paths and the values are those in the leet object at those paths.

function collectZipCodes(customer) { var paths = [ 'billingAddress.zip', 'shippingAddress.zip', 'optionalAddress.zip', 'company.address.zip' ]; return leet(customer).collect(paths); // [63376, 63376, 63301, 63301] }

Constructing and deconstructing data

While l33teral was born from the need to work with object literals over which I had little control (my code consumed them, it did not construct them), I recently found a powerful use case for l33teral’s ability to construct objects. In another, unrelated project I needed to work with data that, for specific reasons, had been divided into two different document collections in mongo. The records in these collections had schemas similar to these:

// customer collection {_id:ObjectId, email:String, role:String, createdAt:Date} // customer data collection {_id:ObjectId, customerID:ObjectId, name:String, value:String}

The customer objects are what you would typically expect: a single document per entity with a unique identifier. The customer data collection was structured so that the relationship to the customer collection was many-to-one with the memberID key. Each record in the customer data collection contained both a name and a value where the values were simple strings and the names were strings in a namespaced “path” format, e.g., ‘billingAddress.street1’.

Working with this customer data on the server is no big deal, but the overhead for clients consuming data via HTTP JSON endpoints would be tedious if they had to parse a collection of “customer data” each time they needed a piece of information like a phone number, zip code, or web page. Clients expect object graphs, not arrays of “records”. This seemed like an ideal candidate for l33teral.

// I *could* return this... { _id: 1, email: 'clark@thedailyplanet.com', role: 'customer', createdAt: '2014-01-01 00:00:00', data: [ {_id: 2, memberID: 1, name: 'name.first', value: 'Clark'}, {_id: 3, memberID: 1, name: 'name.last', value: 'Kent'}, {_id: 4, memberID: 1, name: 'billingAddress.city', value: 'Metropolis'}, {_id: 5, memberID: 1, name: 'archEnemy', value: 'Lex Luthor'}, // etc. ] } // but I *want* to return this { _id: 1, email: 'clark@thedailyplanet.com', role: 'customer', createdAt: '2014-01-01 00:00:00', name: { first: 'Clark', last: 'Kent' }, billingAddress: { city: 'Metropolis' }, archEnemy: 'Lex Luthor' }

To construct a single “customer” object during an HTTP GET, I fetched the customer and his data records asynchronously then built up a single object graph with l33teral.plant() . This was fairly painless.

In the examples below, I use mongoose models to fetch and persist data, and the async library to organize asynchronous control flow of operations. The express web server is also assumed, as each example is an HTTP endpoint.

app.get('/customer/:id', function (req, res, next) { var id = req.params.id; function fetchCustomer(cb) { // one customer record, via mongoose Customer.findById(id, function (err, customer) { // assume no errors // mongoose record to simple literal cb(null, customer.toObject()); }); } function fetchData(cb) { // many data records, via mongoose CustomerData.find({customerID: id}, function (err, customerData) { // assume no errors // mongoose records to simple literals cb(null, customerData.map(function (d) { return d.toObject(); })); }); } async.parallel([ fetchCustomer, fetchData ], function (err, results) { // assume no errors! // results from each operation are in // the results array, positioned ordinally // according to the order of functions passed // to async.parallel var customer = leet(results[0]), data = results[1]; // plant each customer data value data.forEach(function (d) { customer.plant(d.name, d.value); }); // the `obj` property returns the original, mutated // object literal res.json(200, customer.obj); }); });

I then returned this object graph to the client as a single JSON object, with all expected properties. So far so good. The second challenge was how to break up an object graph into its constituent data records on a POST or PUT.

There were several things that needed to happen. First, I needed to know which paths to extract from the customer literal. Because I was dynamically building these literals for client consumers, I kept these in our data store, but for this example, I will just assume they are in a simple array.

var pathsToExtract = [ 'name.first', 'name.last', 'billingAddress.city', 'archEnemy' ];

Next, I mapped each path to its corresponding point of data on the customer literal with l33teral’s extract() method. When I had this data, I used the purge() method to recursively remove these paths from the customer object, so that only the paths that were native to the customer object remained. This included metadata like timestamps, IDs, user role, etc. Once I’d separated the customer object from its many data points, I persisted all records back to the data store. I used the async library’s waterfall() method to chain these operations together, ensuring that the customer was saved before his data. I generated a 200 response when finished to indicate that the operation had been successful.

app.put('/customer', function (req, res, next) { var customer = req.body, id = customer._id; var leetCustomer = leet(customer); var dataHash = leetCustomer.extract(pathsToExtract); // result: // { // 'name.first': 'Clark', // 'name.last': 'Kent', // etc. // } // leave *only* the paths on the literal that are // native to the customer record leetCustomer.purge(pathsToExtract); function persistCustomer(cb) { // the `obj` property returns the modified literal Customer.update(leetCustomer.obj, cb); } function persistData(cb) { // iterate over the data hash keys, and create a record // for each name/value pair var pathsToSave = Object.keys(dataHash); async.each(pathsToSave, function (path, cb) { var searchCriteria = {customerID: id, name: path}; var value = {value: dataHash[path]}; var options = {upsert: true}; // perform an upsert to ensure that records // that already exist get updated, and new // records get added; assume no deletions CustomerData.update(searchCriteria, value, options, cb); }, cb); } async.waterfall([ persistCustomer, persistData ], function (err) { // assume no error res.json(200, {customerID: id}) }); });

This solution, while involved, worked well for the needs that I had. l33teral made it much easier to build out and deconstruct my objects without having lots of recursive code scattered across my application (it’s all in l33teral instead!).

Limitations

l33teral does have some limitations, albeit sensical ones. As the library’s name implies, it is designed to work with object literals, not complex object graphs where certain parts of the graph override members of their prototypes.

Consider the following object graph, created with the node.js REPL:

# var o1 = {foo: 'bar'}; undefined # var o2 = Object.create(o1); undefined # o2.foo 'bar' # o2.foo = 'baz'; 'baz' # var o3 = {o2: o2}; undefined # o3.o2.foo 'baz'

The object graph corresponds to this model:

When I query o3.o2.foo in this example, I get the value ‘baz’, set explicitly as a property on object o2. l33teral would detect this property and report back that, yes, the path o3.o2.foo does exist.

leet(o3).probe('o2.foo'); //true

If I were to use l33teral’s snip() method, however, to remove the property foo from the object o2 , I would get unexpected results:

leet(o3).snip('o2.foo'); o3.o2.foo; // would expect `undefined`, but is actually the value `bar`

This happens because l33teral does not anticipate graph objects that override properties in their prototypes. We can simulate this in the node.js REPL as well.

# delete o3.o2.foo true # o3.o2.foo 'bar'

Probing o3.o2.foo would also return false because object o2 no longer has its own property foo . A demonstration of this limitation can be viewed live in this jsbin snip.

Conclusion

For better or worse, JavaScript developers have to work with object literals. Understanding how they work and what you can and cannot do with them helps developers mature in knowledge and practice, but once you’ve gained this knowledge l33teral can give you shortcuts that will just make your life easier. Sometimes it is better to correct problems upstream before they get to us (explicit message schemas on your message bus, default values on object literals so you always know which properties will be present, etc.), but sometimes we have no control over these things and the best we can do is “[l]ive as brave men; and if fortune is adverse, front its blows with brave hearts.” (Cicero) So if you’re plagued by literals that you can’t control, grab a copy of l33teral and tame the beast. You’ll be happy you did.

Learn more about the l33teral API with this Blazon presentation!

https://presentboldly.com/nicholascloud/l33t-literals-in-javascript