xJson Objects


Alright everyone, I have finished the xJSON object suite, or at least a decent first pass. This was a bigger project than I expected and I apologize for dragging it out over the past couple weeks. It seemed like every time I was about to finish, I would uncover a corner case that would break an entire second of the project. Anyway, I was away over the passed weekend and had to do a lot work in the evening last week, and missed your Tuesday post as well. I hope to be better about getting two articles written a week. I am also open to guest posts, if you are interest just leave a comment on any article.

Anyway, I have written the following objects: JsonObject, JsonArray, XJsonObject, and XJsonArray. Here they are:

/**
 * The JsonObject class manages a JSON object by creating getter and setters
 * @namespace Core.Model
 * @class JsonObject
 * @dependencies library
 */
Core.Model.JsonObject = function(json) {
	this.update(json);
};

Core.extend(Core.Model.JsonObject, Core.Model.Model, {

	/**
	 * The number of values in the json object
	 * @property length
	 * @type int
	 */
	length: 0,

	/**
	 * Updates the object to use the passed data set
	 * @method update
	 * @param json {array} jsonobject object
	 * @public
	 */
	update: function(json) {
		// validation
		if (! isType(json, 'object')) {throw('JsonObject - Invalid data passed into Update');}
		this.parent.update.call(this, json);

		// private variables
		var data = {},
			that = this,
			n = 0;
			
		var fx = function(key, o) {
			var ckey = capitalize(key);
			data[key] = Core.Model.XJson(o);
			that[(isType(o, 'boolean') && -1 !== key.indexOf('is'))? key: 'get' + ckey] = function() {return data[key];};
			that['set' + ckey] = function(o) {
				if ($type(o) !== $type(data[key])) {throw('JSONObject - invalid object passed into setter for: ' + key);}
				data[key] = o;
			};
		};

		// iterate through the JSON object keys
		for (var key in json) {
			fx(key, json[key]);
			n += 1;
		};

		// update public variables
		this.length = n;
	}
});

/**
 * The JsonArray class manages ...
 * @namespace Core.Model
 * @class JsonArray
 * @dependencies library
 */
Core.Model.JsonArray = function(json) {
	this.update(json);
};

Core.extend(Core.Model.JsonArray, Core.Model.Model, {

	/**
	 * Boolean used to inform the 'get' method to clean the cache
	 * @property clean
	 * @type boolean
	 */
	clean: false,

	/**
	 * The number of values in the json array
	 * @property length
	 * @type int
	 */
	length: 0,
	/**
	 * Execute function 'fn' on all elements in collection
	 * @method batch
	 * @param fn {function} the function to execute
	 * @public
	 */
	batch: function(fn) {
		Core.batch(this.data, function(o, i, scope) {
			fn(scope.get(i), i);
		}, this);
	},

	/**
	 * Retrieve the element at 'i' from the collection; lazy definition to allow for converting objects on demand
	 * @method get
	 * @param i {int} index in the data
	 * @public
	 */
	get: function(i) {
		var that = this,
			data = [];

		this.get = function(i) {
			// commented because this is very strict and don't always want to use, sometimes returning undefined is ok
			//if (! that.data[i]) {throw('JsonArray - Invalid index passed into Get: ' + i);}
			// purges cached data on an update
			if (that.clean) {
				data = [];
				that.clean = false;
			}
			
			if (! data[i] && that.data[i]) {
				data[i] = Core.Model.XJson(that.data[i]);
			}

			return data[i];
		};

		return this.get(i);
	},

	/**
	 * Inserts another value into the data structure
	 * @method push
	 * @param o {array} new row of data
	 * @public
	 */
	push: function(o) {
		if (! o) {throw('JsonArray - Invalid Object passed into Push');}
		this.data.push(o);
		this.length = this.data.length;
	},

	/**
	 * Updates the object to use the passed data set
	 * @method update
	 * @param json {array} jsonarray object
	 * @public
	 */
	update: function(json) {
		// validation
		if (! isType(json, 'array')) {throw('JSONArray - Invalid JSON Array Object passed into Update');}
		this.parent.update.call(this, json);
		this.clean = true;

		// update public variables
		this.length = json.length;
	}
});

/**
 * The XJsonObject class manages ...
 * @namespace Core.Model
 * @class XJsonObject
 * @dependencies library
 */

Core.Model.XJsonObject = function(scheme, json) {
	if (! isType(scheme, 'array')) {throw('XJsonObject - Invalid scheme passed into Constructor');}
	this.scheme = scheme;
	this.update(json);
};

Core.extend(Core.Model.XJsonObject, Core.Model.Model, {

	/**
	 * The number of values in the xjson object
	 * @property length
	 * @type int
	 */
	length: 0,

	/**
	 * The object schema
	 * @property scheme
	 * @type array
	 */
	scheme: [],

	/**
	 * Updates the object to use the passed data set
	 * @method update
	 * @param json {object} XJsonObject object
	 * @public
	 */
	update: function(json) {
		// validation
		if (! isType(json, 'array')) {throw('XJsonObject - Invalid data passed into Update');}
		if (json.length !== this.scheme.length) {throw('XJsonObject - Invalid data (does not match scheme) passed into Update');}
		this.parent.update.call(this, json);

		// private variables
		var data = this.data,
			that = this;

		// update public variables
		this.length = data.length;

		Core.batch(this.scheme, function(key, i, json) {
			var o = json[i],
				ckey = capitalize(key);

			data[i] = Core.Model.XJson(o);

			that[(isType(o, 'boolean') && -1 !== key.indexOf('is'))? key: 'get' + ckey] = function() {return data[i];};
			that['set' + ckey] = function(o) {
				if ($type(o) !== $type(data[key])) {throw('XJSONObject - invalid object passed into setter for: ' + key);}
				data[i] = o;
			};
		}, data);
	}
});

/**
 * The XJsonArray class manages ...
 * @namespace Core.Model
 * @class XJsonArray
 * @dependencies library
 */
Core.Model.XJsonArray = function(json) {
	this.update(json);
};

Core.extend(Core.Model.XJsonArray, Core.Model.Model, {

	/**
	 * Boolean used to inform the 'get' method to clean the cache
	 * @property clean
	 * @type boolean
	 */
	clean: false,

	/**
	 * The number of values in the xjson array
	 * @property length
	 * @type int
	 */
	length: 0,

	/**
	 * The object schema
	 * @property scheme
	 * @type array
	 */
	scheme: [],

	/**
	 * Execute function 'fn' on all elements in collection
	 * @method batch
	 * @param fn {function} the function to execute
	 * @public
	 */
	batch: function(fn) {
		Core.batch(this.data, function(o, i, scope) {
			fn(scope.get(i), i);
		}, this);
	},
	
	/**
	 * Retrieve the element at 'i' from the collection; lazy definition to allow for converting objects on demand
	 * @method get
	 * @param i {int} index in the data
	 * @public
	 */
	get: function(i) {
		var that = this,
			data = [];

		// internally redeclaring to scope the data structure, which will contain already converted objects
		this.get = function(i) {
			// commented because this is very strict and don't always want to use, sometimes returning undefined is ok
			//if (! that.data[i]) {throw('XJsonArray - Invalid index passed into Get: ' + i);}
			// purges cached data on an update
			if (that.clean) {
				data = [];
				that.clean = false;
			}
			
			if (! data[i]) {
				var o = that.data[i];

				// if array, then it is an XJsonObject
				if (isType(o, 'array')) {
					data[i] = new Core.Model.XJsonObject(that.scheme, o);
				}
				// otherwise, use generic method
				else {
					data[i] = Core.Model.XJson(o);
				}
			}

			return data[i];
		};

		return this.get(i);
	},

	/**
	 * Inserts another value into the data structure
	 * @method push
	 * @param o {array} new row of data
	 * @public
	 */
	push: function(o) {
		if (! (isType(o, 'array') && o.length === this.scheme.length)) {throw('XJsonArray - Invalid data passed into Push');}
		this.data.push(o);
		this.length = this.data.length;
	},

	/**
	 * Updates the object to use the passed data set
	 * @method update
	 * @param json {object} xjsonarray object
	 * @public
	 */
	update: function(json) {
		// validation
		if (! (json && isType(json, 'object') && isType(json.scheme, 'array') && isType(json.set, 'array'))) {throw('XJsonArray - Invalid data passed into Update');}
		this.parent.update.call(this, json.set);
		this.clean = true;

		// update public variables
		this.length = json.length;
		this.scheme = json.scheme;
	}
});

/**
 * The XJson class manages ...
 * @namespace Core.Model
 * @class XJson
 * @dependencies library
 */

Core.Model.XJson = function(o) {
	if (! o) {
		return null;
	}
	else if (isType(o, 'array') && (isType(o[0], 'array') || ! o.length)) {
		return new Core.Model.JsonArray(o);
	}
	else if (isType(o, 'object') && ! isType(o, 'array')) {
		return (o.scheme)? new Core.Model.XJsonArray(o): new Core.Model.JsonObject(o);
	}
	else {
		return o;
	}
};

This is a lot of code to discuss, which I do not have time for today (Tuesday's article will have a detailed explanation). I have introduced a String manipulation method 'capitalize', which converts strings like 'mattsnider' to 'Mattsnider'. This way all getter and setter methods have the appropriate capitalization. This method is from a small package of string functions, which we will discuss after these JSON objects. For now, let me know your thoughts about these objects and take a look at the test page.