(function($) {

    /**
    * Go through the current JQuery match, and build a
    * javascript object based on the name attributes of
    * child elements. This is intended to be used for forms
    * with inputs: var jsObj = $('#myform').formToObject();
    *
    * Using dots in the name attributes will build nested
    * structures into the resulting object:
    *
    *      <input name="foo.bar" value="x" />
    *                 returns
    *            { foo: { bar:"x" } }
    *
    * The same name appearing twice will be returned as an array:
    *
    *  <input type="checkbox" name="foo" value="a" checked="true"/>
    *  <input type="checkbox" name="foo" value="b" checked="true"/>
    *                 returns
    *           { foo: ['a','b'] }
    *
    * Checkboxes and Radio inputs which are not checked will not
    * appear at all, nor will any inputs which are disabled.
    *
    * Form fields which are optional can be omitted from the resulting
    * object by providing a value for that field of "__OMIT__".
    *
    */
    "use strict";

    $.fn.formToObject = function() {
        // don't want to depend on other libs. Provide our own size to check
        // length of an associative array:
        function _size(obj) {
            var key, size=0;
            for (key in obj) {
                if (obj.hasOwnProperty(key)) size++;
            }
            return size;
        }

        var formObject = {},
            a = this.serializeArray();

        $.each(a, function() {
            var data = $('[name="' + this.name + '"]').first().data(),
                current = formObject,
                childKey,
                path = this.name.split('.'),
                key = path.pop(),
                val = (data && data.parseInt !== undefined && data.parseInt) ? parseInt(this.value, 10) : (
                        (this.value.toLowerCase() == 'true') ? true : (
                            (this.value.toLowerCase() == 'false') ? false : (
                                (this.value.toLowerCase() == 'null') ? null : (
                                    (typeof this.value == 'undefined') ? '' : this.value
                                )
                            )
                        )
                    );

            while (path.length > 0) {
                // Shift the front element from path into childKey
                childKey = path.shift();
                if (!(childKey in current)) {
                    current[childKey] = {};
                }
                // Move down and start working with the child object
                current = current[childKey];
            }

            // We're at the right level in hierarchy. Define property next.
            if (key in current) { // already exists.
                if ($.isPlainObject(current[key])) { // about to overwrite obj with property!
                    throw "Can't overwrite named structure";
                }
                if (!current[key].push) { // convert property to array
                    current[key] = [current[key]];
                }
                current[key].push(val);
            } else { // doesn't exist. Just set.
                if (val !== '__OMIT__') {
                    current[key] = val;
                }
            }
        });

        // Trim any keys who hold nothing but empty objects: {}

        var removeEmptyObjects = function(o) {
            var k;
            var trimlist = [];
            for (k in o) {
                if ($.isPlainObject(o[k])) { // recurse: deep first
                    removeEmptyObjects(o[k]);
                }
                if ($.isPlainObject(o[k]) && _size(o[k]) === 0) {
                    trimlist.push(k);
                }
            }
            $.each(trimlist, function() {
                delete o[this];
            });
        };

        removeEmptyObjects(formObject);
        return (_size(formObject) === 0) ? null : formObject;
    };

})(jQuery);