Tuesday, May 28, 2013

JSON Validator

For a while I have been developing a NodeJS web app ~ The entire app is service/ajax/socket driven and requires a lot of message passing. I quickly discovered I needed a convenient yet secure way to validate all messages arriving at the server. Enter JSONValidate.js
This one function can be used on both ends of my app to proactively (on the client) and reactively (on the server) bounce faulty data. I have been checking in on the JSON schema standard over the past three years, but I still wanted my schema to have a few more bells and whistles than what the standard was providing ~ So stick it for now, this works fine.

Download

If you're anything like me and don't want to read all my mumbo-jumbo, then you can just go ahead and view and download the code.

Schema Breakdown ~ Let's Build One

A schema is simply a JSON object with a set of properties appropriately named as desired object field names. Let's make a human schema.
var human = {
  name: { },
  birthday: { },
  weight: { },
  gender: { },
  deceased: { }
};
Now we can define type for each property.
var human = {
  name: { type: 'string' },
  birthday: { type: 'string' },
  weight: { type: 'number' },
  gender: { type: 'string' },
  deceased: { type: 'boolean' }
};
At this stage the schema would validate an object with four, non-null appropriately named properties. Let's throw in some extra boundaries and rules...
var human = {
  name: { type: 'string', min: 3, max: 120 },
  birthday: { type: 'string', regex: /^(19|20)\d\d([- \/.])(0[1-9]|1[012])\2(0[1-9]|[12][0-9]|3[01])$/ },
  weight: { type: 'number', null: true },
  gender: { type: 'string', min: 1, max: 1 },
  deceased: { type: 'boolean' }
};
  • All properties are assumed to be required (not null) unless so specified (like the weight property).
  • The regex property is used on string fields. The regex for birthday is simply a date regular expression in format yyyy-mm-dd.
  • min and max can be used on string and number types.
Next: we can define label and custom.
  • If a label property is defined and the object being validated fails, the validator will use the label property to describe the error.
  • The custom property allows for a custom function to be defined in the schema. The function will be given two parameters; the property value, and the object being validated, in that order. The custom function should return an array of strings in the event of data not passing validation; and simply return nothing if validation was successful.
var human = {
  name: { type: 'string', min: 3, max: 120, label: 'Full name' },
  birthday: { type: 'string', regex: /^(19|20)\d\d([- \/.])(0[1-9]|1[012])\2(0[1-9]|[12][0-9]|3[01])$/ },
  weight: { type: 'number', null: true },
  gender: { type: 'string', min: 1, max: 1
    custom: function(v) {
      if (['F', 'M'].indexOf(v.toUpperCase()) == -1) 
        return ['Gender must be set to F or M.'];
    } 
  },
  deceased: { type: 'boolean' }
};
The last two features worth mention: arrays and sub schemas are both supported as well. Let's add some pets to our human.
var animal = {
  name: { type: 'string', min: 3, max: 120 }
};

var human = {
  name: { type: 'string', min: 3, max: 120, label: 'Full name' },
  birthday: { type: 'string', regex: /^(19|20)\d\d([- \/.])(0[1-9]|1[012])\2(0[1-9]|[12][0-9]|3[01])$/ },
  weight: { type: 'number', null: true },
  gender: { type: 'string', min: 1, max: 1,
    custom: function(v) {
      if (['F', 'M'].indexOf(v.toUpperCase()) == -1) 
        return ['Gender must be set to F or M.'];
    } 
  },
  deceased: { type: 'boolean' },
  pets: { type: 'object', schema: animal, array: true, null: true }
};
* Recursive behavior is not yet supported. (sad face) The last code snippet above should validate positively on an object that looks like this:
{
  name: 'Victor Challis',
  birthday: '1982-12-14',
  weight: null,
  gender: 'M',
  deceased: false,
  pets: [
    { name: 'Alex' }, 
    { name: 'Pat' }
  ]
};
In case you didn't see it at the top, you can experiment with the validator on JSFiddle.net.

No comments: