JavaScript object serializer source code

var Ant = {};

Ant.Serializer = function () {
	this._classMap = new Ant.Serializer.ClassMap();
};

Ant.Serializer.setDefaultStorage = function (storage)
{
	Ant.Serializer._defaultStorage = storage;
};

Ant.Serializer.getDefaultStorage = function ()
{
	if (null == Ant.Serializer._defaultStorage) {
		Ant.Serializer._defaultStorage = new Ant.Serializer[Ant.Serializer._defaultStorageName];
	}
	return Ant.Serializer._defaultStorage;
};

Ant.Serializer._defaultStorage = null;
Ant.Serializer._defaultStorageName = 'XmlStorage';

with (Ant.Serializer)
{

	prototype.register = function (name, constr)
	{
		this._classMap.register(name, constr);
	};

	prototype.getClassMap = function ()
	{
		return this._classMap;
	};

	prototype.save = function (object, writer)
	{
		if (!writer) {
			writer = Ant.Serializer.getDefaultStorage().createWriter(this);
		}
		this._saveObject(object, writer);
		return writer;
	};

	prototype._saveObject = function (object, writer)
	{
		var objectWriter = writer.writeObject(object);
		for (var p in object) {
			this._saveProperty({ 'name' : p }, object[p], objectWriter);
		}
		return objectWriter;
	};

	prototype._saveProperty = function (property, value, objectWriter)
	{
		if ('object' == typeof value && null != value) {
			var propObjectWriter = this._saveObject(value, objectWriter.getWriter());
			objectWriter.writeProperty(property, propObjectWriter.getObjectRef());
		} else {
			objectWriter.writeProperty(property, value);
		}
	};

	prototype.load = function (arg)
	{
		if (arg instanceof Ant.Serializer.IReader) {
			return this._loadObject(arg);
		}
		return this._loadObject(Ant.Serializer.getDefaultStorage().createReader(arg, this));
	};

	prototype._loadObject = function (reader, ref)
	{
		var objectReader;
		if ('undefined' == typeof ref) {
			objectReader = reader.readObject()
		} else {
			objectReader = reader.readObject(ref);
		}
		var object = objectReader.getObject();
		for (var p in object) {
			this._loadProperty({ 'name' : p }, object, objectReader);
		}
		return object;
	};


	prototype._loadProperty = function (property, object, objectReader)
	{
		var ref = objectReader.tryGetObjectRef(property);
		if (null == ref) {
			objectReader.readProperty(property, object);
		} else {
			object[property.name] = this._loadObject(objectReader.getReader(), ref);
		}
	};

}

Ant.Serializer.IReader = function ()
{};

Ant.Serializer.IObjectRef = function ()
{};

Ant.Serializer.ClassMap = function ()
{
	this._nameConstructorMap = {};
	this._constructorNameMap = {};
	this.register('Date', new Date().constructor);
};

with (Ant.Serializer.ClassMap)
{

	prototype.register = function (name, constr)
	{
		this._nameConstructorMap[name] = constr;
		this._constructorNameMap[constr] = name;
	};

	prototype.constr = function (name)
	{
		return this._nameConstructorMap[name];
	};

	prototype.name = function (obj)
	{
		var constructor = obj.constructor;
		var constr = constructor.toString().replace(/^\s*/, '');
		var type = constr.substr(8, constr.indexOf('(') - 8).replace(/[\s\n\r]*/g, '');
		if ('' == type) {
			return this._constructorNameMap[constructor.toString()];
		}
		return type;
	};

}

/**
 * Xml Storage incapsulates logic, specific to 
 * saving objects to XML string.
 */
Ant.Serializer.XmlStorage = function ()
{
	this._adapter = null;
};

with (Ant.Serializer.XmlStorage)
{

	prototype.createWriter = function (serializer)
	{
		return new Ant.Serializer.XmlStorage.Writer(this, serializer);
	};

	prototype.createReader = function (data, serializer)
	{
		return new Ant.Serializer.XmlStorage.Reader(data, this, serializer);
	};

	prototype._createAdapter = function ()
	{
		var adapter = '';
		if ('undefined' != typeof ActiveXObject) {
			adapter = 'MS';
		} else if ('undefined' != typeof document
			&& document.implementation
			&& document.implementation.createDocument
			&& 'undefined' != typeof DOMParser)
		{
			adapter = 'default';
		}
		switch (adapter) {
			case 'MS':
				return new (function () {
					this.createDocument = function () {
						var names = ["Msxml2.DOMDocument.6.0",
							"Msxml2.DOMDocument.3.0", "MSXML2.DOMDocument",
							"MSXML.DOMDocument", "Microsoft.XMLDOM"];
						for (var key in names) {
							try {
								return new ActiveXObject(names[key]);
							} catch (e) {}
						}
						throw new Error('Unable to create DOMDocument');
					};
					this.serialize = function (doc) {
						return doc.xml;
					};
					this.parseXml = function (xml) {
						var doc = this.createDocument();
						if (!doc.loadXML(xml)) {
							throw new Error('Parse error');
						}
						return doc;
					};
				})();
			case 'default':
				return new (function () {
					this.createDocument = function () {
						return document.implementation.createDocument("", "", null);
					};
					this.serialize = function (doc) {
						return new XMLSerializer().serializeToString(doc);
					};
					this.parseXml = function (xml) {
						var doc = new DOMParser().parseFromString(xml, "text/xml");
						if ("parsererror" == doc.documentElement.nodeName) {
							throw new Error('Parse error');
						}
						return doc;
					};
				})();
			default:
				throw new Error('Unable to select the DOM adapter');
		}
	};

	prototype.getDomAdapter = function ()
	{
		if (null == this._adapter) {
			this._adapter = this._createAdapter();
		}
		return this._adapter;
	};

}

Ant.Serializer.XmlStorage.Writer = function (storage, serializer)
{
	this._serializer = serializer;
	this._storage = storage;
	this._document = this._storage.getDomAdapter().createDocument();
};

with (Ant.Serializer.XmlStorage.Writer)
{

	prototype.writeObject = function (obj)
	{
		var name = this._serializer.getClassMap().name(obj);
		var node = this._document.createElement(name);
		if (!this._document.documentElement) {
			this._document.appendChild(node);
		}
		if ('Date' == name) {
			node.setAttribute('value', obj.getUTCFullYear() + '-'
				+ obj.getUTCMonth() + '-' + obj.getUTCDate()
				+ 'T' + obj.getUTCHours() + ':' + obj.getUTCMinutes()
				+ ':' + obj.getUTCSeconds() + '.' + obj.getUTCMilliseconds());
		}
		return new Ant.Serializer.XmlStorage.ObjectWriter(this, node);
	};

	prototype.toString = function ()
	{
		return this._storage.getDomAdapter().serialize(this._document);
	};

}

Ant.Serializer.XmlStorage.ObjectWriter = function (writer, node)
{
	this._writer = writer;
	this._node = node;
};

with (Ant.Serializer.XmlStorage.ObjectWriter)
{

	prototype.writeProperty = function (property, value)
	{
		var doc = this._node.ownerDocument;
		var element = this._node.appendChild(doc.createElement(property.name));
		if (value instanceof Ant.Serializer.IObjectRef) {
			element.appendChild(value._data);
		} else if (null != value) {
			if (!property.type) {
				element.setAttribute('type', typeof value);
			}
			element.appendChild(doc.createTextNode(value.toString()));
		}
	};

	prototype.getWriter = function ()
	{
		return this._writer;
	};

	prototype.getObjectRef = function ()
	{
		var ref = new Ant.Serializer.IObjectRef();
		ref._data = this._node;
		return ref;
	};
}

Ant.Serializer.XmlStorage.Reader = function (data, storage, serializer)
{
	this._serializer = serializer;
	this._storage = storage;
	this._document = this._storage.getDomAdapter().parseXml(data);
};

with (Ant.Serializer.XmlStorage.Reader)
{
	prototype = new Ant.Serializer.IReader();

	prototype.readObject = function (ref)
	{
		if ('undefined' == typeof ref) {
			return new Ant.Serializer.XmlStorage.ObjectReader(this, this._document.documentElement);
		}
		return new Ant.Serializer.XmlStorage.ObjectReader(this, ref._data);
	};

	prototype.getSerializer = function ()
	{
		return this._serializer;
	};
}

Ant.Serializer.XmlStorage.ObjectReader = function (reader, node)
{
	this._reader = reader;
	this._node = node;
	var name = this._node.nodeName;
	var constr = this._reader.getSerializer().getClassMap().constr(this._node.nodeName);
	this._object = new constr();
	if ('Date' == name) {
		var parts = this._node.getAttribute('value').split(/[\-\T\:\.]/);
		with (this._object) {
			setUTCFullYear(parts[0]);
			setUTCMonth(parts[1]);
			setUTCDate(parts[2]);
			setUTCHours(parts[3]);
			setUTCMinutes(parts[4]);
			setUTCSeconds(parts[5]);
			setUTCMilliseconds(parts[6]);
		}
	}
};

with (Ant.Serializer.XmlStorage.ObjectReader)
{

	prototype.getObject = function ()
	{
		return this._object;
	};

	prototype.tryGetObjectRef = function (property)
	{
		var propertyNode = this.getChildNodeByName(this._node, property.name);
		if (null != propertyNode.firstChild && 1 /*=ELEMENT*/ == propertyNode.firstChild.nodeType) {
			var ref = new Ant.Serializer.IObjectRef();
			ref._data = propertyNode.firstChild;
			return ref;
		}
		return null;
	};

	prototype.readProperty = function (property, object)
	{
		var node = this.getChildNodeByName(this._node, property.name);
		if (node) {
			if (0 == node.childNodes.length) {
				object[property.name] = null;
			} else {
				var type = (property.type) ? type = property.type : node.getAttribute('type');
				switch (type) {
					case 'number':
						object[property.name] = Number(node.childNodes[0].nodeValue);
						break;
					case 'boolean':
						object[property.name] = ('true' == node.childNodes[0].nodeValue);
						break;
					case 'undefined':
						// do nothing
						break;
					default:
						object[property.name] = node.childNodes[0].nodeValue;
						break;
				}
			}
		}
	};

	prototype.getReader = function ()
	{
		return this._reader;
	};

	prototype.getChildNodeByName = function (node, name) {
		var nl = node.childNodes;
		for (var i = 0; i < nl.length; i++) {
			if (nl.item(i).nodeName == name) {
				return nl.item(i);
			}
		}
		return null;
	};


}