Skip to Content

Validating javascript objects

Validation is an important piece of an effective user interface. It reduces error rates, supports user engagement, and helps clarify the needs of the system on the interface’s receiving end.

We can think of validation in two parts:

  1. Assigning constraints to a user input
  2. Assessing whether an arbitrary input meets those constraints

For a practical example, consider a user registration form. We want to make sure that an a name, e-mail, and password are present, and that the password is accurately confirmed. We arrive at the following constraints:

var validations = {
  email:    { type: 'email' },
  name:     { presence: true },
  password: { presence: true },
  confirm:  { confirm: 'password' }
};

More complicated fields might require more validations, but the same syntax can still be used:

validations.username = {
  presence: true,
  rangeLength: [6, 10] }
};

Just like that, we’re halfway done. We just need a way to determine whether a given input meets each constraint.

Validating a field

Each rule assigned to the user input form can be evaluated using a validation function. There’s no magic here–just a brief test of whether an input meets the rule’s criteria.

var validators = {
  presence: function (attrs, key, present) {
    if (typeof(present) == 'undefined') {
      present = true;
    }
    return (attrs.hasOwnProperty(key) == present && attrs[key] == present);
  },
  match: function (attrs, key, test) {
    if (test instanceof RegExp) {
      return attrs[key].match(test);
    }
    return attrs[key] == test;
  },
  confirm: function (attrs, key, otherKey) {
    return attrs[key] == attrs[otherKey];
  },
  maxLength: function (attrs, key, len) {
    return attrs[key].length < len;
  },
  minLength: function (attrs, key, len) {
    return attrs[key].length > len;
  }
};

More sophisticated validators can be built on the shoulders of the basic set.

// check whether input falls between min and max values
validators.rangeLength = function (attrs, key, min, max) {
  return validators.minLength(attrs, key, min) &&
         validators.maxLength(attrs, key, max);
};

// check whether input follows the given type
validators.type = function (attrs, key, type) {
  var test;
  switch (type) {
    case 'email': test = /[^@]+@.*\.[a-z]{2,4}/i; break;
    case 'alpha': test = /[0-9a-z]+/i; break;
  };
  return validators.match(attrs, key, test);
};

These are just examples–e-mail addresses are notoriously difficult to validate, and the simple regex here will fail in many cases–but they illustrate how rules might be composed. Additional rules might include skipping validations when a field is not present, or to require present in addition to any other validations.

The Runner

Once the rules are established, we need a runner to apply them to a set of input. One option is to implement a function that accepts validations and a set of “subject” attributes, and returns any validations that fail:

var errors = runAllValidations(validations, attributes);

Expanding the runner into two parts, we arrive at a method for evaluating individual inputs:

function runValidation (validation, attributes, key) {
  var options, name, errors = [];
  for (name in validation) {
    options = validation[name];
    if (!(options instanceof Array)) options = [options];
    if (!validators[name].apply(this, [attributes, key].concat(options))) {
      errors.push(name);
    }
  }
  return errors;
}

And a method that calls it for each value in the attributes hash:

function runAllValidations (validations, attributes) {
  var key, result, valid = true, errors = {};
  for (key in validations) {
    result = runValidation(validations[key], attributes, key);
    if (result.length) {
      valid = false;
      errors[key] = result;
    }
  }
  if (!valid) {
    return errors;   
  }
}

Pass in a hash of attributes and the validations to perform, and runAllValidations will tell us whether it has passed by returning nothing (success) or an object containing the fields and validators that failed. Omitting an e-mail from registration, for instance, would return:

{
  email: ["presence", "type"]
}

Integrating with Backbone

All of our work so far has been framework-agnostic, but plugging it into Backbone couldn’t be easier.

Backbone’s validation API assumes that a validation returning anything truthy has failed. Since the runner’s output is a list of errors, we can plug it in and Backbone.Model‘s validation-related functions won’t even notice the difference.

// in model prototype ...
validate: function (attributes) {
  return runAllValidations(validations, attributes);
}

The truly lazy can even use underscore’s partial to attach the validator to the model without an explicit definition:

validate: _.partial(runAllValidations, validations)

Check it out in a quick fiddle.

The Aftermath

It’s time to take the list of errors and put it to work. How this is done will vary depending on how the interface is designed, but for simple forms a little bit of jQuery can work wonders:

var errors = runAllValidations(validations, attributes);
for (key in errors) {
  $('[name="' + key + '"]').addClass('error');
}

And no article on validation would be complete without the obligatory reminder to never trust the client. Don’t do it! Validating in a browser? Be sure to repeat the validations on the server side.

Related projects

This article outlines a general strategy, but odds are that [your popular open-source framework] already has a robust validation library of its own. Two highly recommended choices are: