Wednesday, May 30, 2012

Convert form data to JS object with jQuery


How do I convert all elements of my form to a JS object?



I'd like to have some way of automatically building a JS object from my form, without having to loop over each element. I do not want a string, as returned by $('#formid').serialize(); , nor do I want the map returned by $('#formid').serializeArray();


Source: Tips4all

19 comments:

  1. serializeArray already does exactly that, you just need to massage the data into your required format:

    $.fn.serializeObject = function()
    {
    var o = {};
    var a = this.serializeArray();
    $.each(a, function() {
    if (o[this.name] !== undefined) {
    if (!o[this.name].push) {
    o[this.name] = [o[this.name]];
    }
    o[this.name].push(this.value || '');
    } else {
    o[this.name] = this.value || '';
    }
    });
    return o;
    };


    Example of this in action:
    http://jsfiddle.net/sxGtM/3/

    Edit: Updated code to fix bug when submitting multiple values for a single field, at least one of which is an empty string - thanks Daniel.

    ReplyDelete
  2. A fixed version of Tobias Cohen's solution. This one correctly handles falsy values like 0 and ''.

    jQuery.fn.serializeObject = function() {
    var arrayData, objectData;
    arrayData = this.serializeArray();
    objectData = {};

    $.each(arrayData, function() {
    var value;

    if (this.value != null) {
    value = this.value;
    } else {
    value = '';
    }

    if (objectData[this.name] != null) {
    if (!objectData[this.name].push) {
    objectData[this.name] = [objectData[this.name]];
    }

    objectData[this.name].push(value);
    } else {
    objectData[this.name] = value;
    }
    });

    return objectData;
    };


    And a CoffeeScript version for your coding convenience:

    jQuery.fn.serializeObject = ->
    arrayData = @serializeArray()
    objectData = {}

    $.each arrayData, ->
    if @value?
    value = @value
    else
    value = ''

    if objectData[@name]?
    unless objectData[@name].push
    objectData[@name] = [objectData[@name]]

    objectData[@name].push value
    else
    objectData[@name] = value

    return objectData

    ReplyDelete
  3. Check this lib (not a jQuery, but its small and does exaclty what you're need):
    http://code.google.com/p/form2js/

    The author has since added jQuery plugin support at his github page
    https://github.com/maxatwork/form2js

    Serializes complicated forms well.

    ReplyDelete
  4. Convert forms to JSON LIKE A BOSS

    Github: Follow along on Github

    The following code can take work with all sorts of input names; and handle them just as you'd expect.

    E.g.,

    <!-- all of these will work! -->
    <input name="honey[badger]" value="a">
    <input name="wombat[]" value="b">
    <input name="hello[panda][]" value="c">
    <input name="animals[0][name]" value="d">
    <input name="animals[0][breed]" value="e">
    <input name="crazy[1][][wonky]" value="f">
    <input name="dream[as][vividly][as][you][can]" value="g">


    // output
    {
    "honey":{
    "badger":"a"
    },
    "wombat":["b"],
    "hello":{
    "panda":["c"]
    },
    "animals":[
    {
    "name":"d",
    "breed":"e"
    }
    ],
    "crazy":[
    null,
    [
    {"wonky":"f"}
    ]
    ],
    "dream":{
    "as":{
    "vividly":{
    "as":{
    "you":{
    "can":"g"
    }
    }
    }
    }
    }
    }


    Usage

    $('#my-form').toJSON();


    The sorcery

    (function($){
    $.fn.toJSON = function(options){

    options = $.extend({}, options);

    var self = this,
    json = {},
    push_counters = {},
    patterns = {
    "validate": /^[a-zA-Z][a-zA-Z0-9_]*(?:\[(?:\d*|[a-zA-Z0-9_]+)\])*$/,
    "key": /[a-zA-Z0-9_]+|(?=\[\])/g,
    "push": /^$/,
    "fixed": /^\d+$/,
    "named": /^[a-zA-Z0-9_]+$/
    };


    this.build = function(base, key, value){
    base[key] = value;
    return base;
    };

    this.push_counter = function(key){
    if(push_counters[key] === undefined){
    push_counters[key] = 0;
    }
    return push_counters[key]++;
    };

    $.each($(this).serializeArray(), function(){

    // skip invalid keys
    if(!patterns.validate.test(this.name)){
    return;
    }

    var k,
    keys = this.name.match(patterns.key),
    merge = this.value,
    reverse_key = this.name;

    while((k = keys.pop()) !== undefined){

    // adjust reverse_key
    reverse_key = reverse_key.replace(new RegExp("\\[" + k + "\\]$"), '');

    // push
    if(k.match(patterns.push)){
    merge = self.build([], self.push_counter(reverse_key), merge);
    }

    // fixed
    else if(k.match(patterns.fixed)){
    merge = self.build([], k, merge);
    }

    // named
    else if(k.match(patterns.named)){
    merge = self.build({}, k, merge);
    }
    }

    json = $.extend(true, json, merge);
    });

    return json;
    };
    })(jQuery);

    ReplyDelete
  5. There really is no way to do this without examining each of the elements. What you really want to know is "has someone else already written a method that converts a form to a JSON object?" Something like the following should work -- note that it will only give you the form elements that would be returned via a POST (must have a name). This is not tested.

    function formToJSON( selector )
    {
    var form = {};
    $(selector).find(':input[name]:enabled').each( function() {
    var self = $(this);
    var name = self.attr('name');
    if (form[name]) {
    form[name] = form[name] + ',' + self.val();
    }
    else {
    form[name] = self.val();
    }
    });

    return form;
    }

    ReplyDelete
  6. Ok, I know this already has a highly upvoted answer, but another similar question was asked recently, and I was directed to this question as well. I'd like to offer my solution as well, because it offers an advantage over the accepted solution: You can include disabled form elements (which is sometimes important, depending on how your UI functions)

    Here is my answer from the other SO question:

    Initially, we were using jQuery's serializeArray() method, but that does not include form elements that are disabled. We will often disable form elements that are "sync'd" to other sources on the page, but we still need to include the data in our serialized object. So serializeArray() is out. We used the :input selector to get all input elements (both enabled and disabled) in a given container, and then $.map() to create our object.

    var inputs = $("#container :input");
    var obj = $.map(inputs, function(n, i)
    {
    var o = {};
    o[n.name] = $(n).val();
    return o;
    });
    console.log(obj);


    Note that for this to work, each of your inputs will need a name attribute, which will be the name of the property of the resulting object.

    That is actually slightly modified from what we used. We needed to create an object that was structured as a .NET IDictionary, so we used this: (I provide it here in case it's useful)

    var obj = $.map(inputs, function(n, i)
    {
    return { Key: n.name, Value: $(n).val() };
    });
    console.log(obj);


    I like both of these solutions, because they are simple uses of the $.map() function, and you have complete control over your selector (so, which elements you end up including in your resulting object). Also, no extra plugin required. Plain old jQuery.

    ReplyDelete
  7. All of these answers seemed so over the top to me. There's something to be said for simplicity. As long as all your form inputs have the name attribute set this should work just jim dandy.


    $('form.myform').submit(function () {
    var $this = $(this)
    , viewArr = $this.serializeArray()
    , view = {};

    for (var i in viewArr) {
    view[viewArr[i].name] = viewArr[i].value;
    }

    //Do stuff with view object here (e.g. JSON.stringify?)
    });

    ReplyDelete
  8. If you are trying to convert all form fields to json in order to submit this form through ajax here is jquery form plugin that does that.

    ReplyDelete
  9. I wouldn't use this on a live site due to XSS attacks and probably plenty of other issues, but here's a quick example of what you could do:

    $("#myform").submit(function(){
    var arr = $(this).serializeArray();
    var json = "";
    jQuery.each(arr, function(){
    jQuery.each(this, function(i, val){
    if (i=="name") {
    json += '"' + val + '":';
    } else if (i=="value") {
    json += '"' + val.replace(/"/g, '\\"') + '",';
    }
    });
    });
    json = "{" + json.substring(0, json.length - 1) + "}";
    // do something with json
    return false;
    });

    ReplyDelete
  10. I prefer this approach because: you don't have to iterate over 2 collections, you can get at things other than "name" and "value" if you need to, and you can sanitize your values before you store them in the object (if you have default values that you don't wish to store, for example).

    $.formObject = function($o) {
    var o = {},
    real_value = function($field) {
    var val = $field.val() || "";

    // additional cleaning here, if needed

    return val;
    };

    if (typeof o != "object") {
    $o = $(o);
    }

    $(":input[name]", $o).each(function(i, field) {
    var $field = $(field),
    name = $field.attr("name"),
    value = real_value($field);

    if (o[name]) {
    if (!$.isArray(o[name])) {
    o[name] = [o[name]];
    }

    o[name].push(value);
    }

    else {
    o[name] = value;
    }
    });

    return o;
    }


    Use like so:

    var obj = $.formObject($("#someForm"));


    Only tested in Firefox.

    ReplyDelete
  11. I found a problem with the selected solution.

    When using forms that have array based names the JQuery serializeArray() function actually dies.

    I have a PHP framework that uses array based field names to allow for the same form to be put onto the same page multiple times in multiple views. This can be handy to put both add, edit and delete on the same page without conflicting form models.

    Since I wanted to seralize the forms without having to take this absolute base functionality out I decided to write my own seralizeArray():

    var $vals = {};

    $("#video_edit_form input").each(function(i){
    var name = $(this).attr("name").replace(/editSingleForm\[/i, '');

    name = name.replace(/\]/i, '');

    switch($(this).attr("type")){
    case "text":
    $vals[name] = $(this).val();
    break;
    case "checkbox":
    if($(this).attr("checked")){
    $vals[name] = $(this).val();
    }
    break;
    case "radio":
    if($(this).attr("checked")){
    $vals[name] = $(this).val();
    }
    break;
    default:
    break;
    }
    });


    Please note: This also works outside of form submit() so if an error occurs in the rest of your code the form won't submit if you place on a link button saying "save changes".

    Also note that this function should never be used to validate the form only to gather the data to send to the server-side for validation. Using such weak and mass-assigned code WILL cause XSS etc.

    ReplyDelete
  12. Turn anything into an object (not unit tested)

    <script type="text/javascript">
    string = {};

    string.repeat = function(string, count)
    {
    return new Array(count+1).join(string);
    }

    string.count = function(string)
    {
    var count = 0;

    for (var i=1; i<arguments.length; i++)
    {
    var results = string.match(new RegExp(arguments[i], 'g'));
    count += results ? results.length : 0;
    }

    return count;
    }

    array = {};

    array.merge = function(arr1, arr2)
    {
    for (var i in arr2)
    {
    if (arr1[i] && typeof arr1[i] == 'object' && typeof arr2[i] == 'object')
    arr1[i] = array.merge(arr1[i], arr2[i]);
    else
    arr1[i] = arr2[i]
    }

    return arr1;
    }

    array.print = function(obj)
    {
    var arr = [];
    $.each(obj, function(key, val) {
    var next = key + ": ";
    next += $.isPlainObject(val) ? array.print(val) : val;
    arr.push( next );
    });

    return "{ " + arr.join(", ") + " }";
    }

    node = {};

    node.objectify = function(node, params)
    {
    if (!params)
    params = {};

    if (!params.selector)
    params.selector = "*";

    if (!params.key)
    params.key = "name";

    if (!params.value)
    params.value = "value";

    var o = {};
    var indexes = {};

    $(node).find(params.selector+"["+params.key+"]").each(function()
    {
    var name = $(this).attr(params.key),
    value = $(this).attr(params.value);

    var obj = $.parseJSON("{"+name.replace(/([^\[]*)/, function()
    {
    return '"'+arguments[1]+'"';
    }).replace(/\[(.*?)\]/gi, function()
    {
    if (arguments[1].length == 0)
    {
    var index = arguments[3].substring(0, arguments[2]);
    indexes[index] = indexes[index] !== undefined ? indexes[index]+1 : 0;

    return ':{"'+indexes[index]+'"';
    }
    else
    return ':{"'+escape(arguments[1])+'"';
    })+':"'+value.replace(/[\\"]/gi, function()
    {
    return "\\"+arguments[0];
    })+'"'+string.repeat('}', string.count(name, ']'))+"}");

    o = array.merge(o, obj);
    });

    return o;
    }
    </script>


    The output of test:

    $(document).ready(function()
    {
    console.log(array.print(node.objectify($("form"), {})));
    console.log(array.print(node.objectify($("form"), {selector: "select"})));
    });


    on

    <form>
    <input name='input[a]' type='text' value='text'/>
    <select name='input[b]'>
    <option>select</option>
    </select>

    <input name='otherinput[c][a]' value='a'/>
    <input name='otherinput[c][]' value='b'/>
    <input name='otherinput[d][b]' value='c'/>
    <input name='otherinput[c][]' value='d'/>

    <input type='hidden' name='anotherinput' value='hidden'/>
    <input type='hidden' name='anotherinput' value='1'/>

    <input type='submit' value='submit'/>
    </form>


    will yield:

    { input: { a: text, b: select }, otherinput: { c: { a: a, 0: b, 1: d }, d: { b: c } }, anotherinput: 1 }
    { input: { b: select } }

    ReplyDelete
  13. I found a problem with Tobias Cohen's code (I don't have enough points to comment on it directly), which otherwise works for me. If you have two select options with the same name, both with value="", the original code will produce "name":"" instead of "name":["",""]

    I think this can fixed by adding " || o[this.name] == ''" to the first if condition:

    $.fn.serializeObject = function()
    {
    var o = {};
    var a = this.serializeArray();
    $.each(a, function() {
    if (o[this.name] || o[this.name] == '') {
    if (!o[this.name].push) {
    o[this.name] = [o[this.name]];
    }
    o[this.name].push(this.value || '');
    } else {
    o[this.name] = this.value || '';
    }
    });
    return o;
    };

    ReplyDelete
  14. I like samuels version, but I believe it has a small error. Normally JSON is sent as


    {"coreSKU":"PCGUYJS","name_de":"whatever",...


    NOT as


    [{"coreSKU":"PCGUYJS"},{"name_de":"whatever"},...


    so the function IMO should read:

    App.toJson = function( selector ) {
    var o = {};
    $.map( $( selector ), function( n,i )
    {
    o[n.name] = $(n).val();
    });
    return o;
    }


    and to wrap it in data array (as commonly expected, too), and finally send it as astring
    App.stringify( {data:App.toJson( '#cropform :input' )} )

    For the stringify look at Question 3593046 for the lean version, at json2.js for the every-eventuality-covered version. That should cover it all :)

    ReplyDelete
  15. My project does, create standard JSON (with nested) from Forms or populate form with json using jQuery
    https://github.com/milfont/jsonform

    ReplyDelete
  16. The serialize function take JSON object as a parameter and return serialize String.

    function serialize(object) {
    var _SPECIAL_CHARS = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, _CHARS = {
    '\b' : '\\b',
    '\t' : '\\t',
    '\n' : '\\n',
    '\f' : '\\f',
    '\r' : '\\r',
    '"' : '\\"',
    '\\' : '\\\\'
    }, EMPTY = '', OPEN_O = '{', CLOSE_O = '}', OPEN_A = '[', CLOSE_A = ']', COMMA = ',', COMMA_CR = ",\n", CR = "\n", COLON = ':', space = "", COLON_SP = ': ', stack = [], QUOTE = '"';
    function _char(c) {
    if (!_CHARS[c]) {
    _CHARS[c] = '\\u' + ('0000' + (+(c.charCodeAt(0))).toString(16))
    .slice(-4);
    }
    return _CHARS[c];
    }
    function _string(s) {
    return QUOTE + s.replace(_SPECIAL_CHARS, _char) + QUOTE;
    // return str.replace('\"','').replace('\"','');
    }

    function serialize(h, key) {
    var value = h[key], a = [], colon = ":", arr, i, keys, t, k, v;
    arr = value instanceof Array;
    stack.push(value);
    keys = value;
    i = 0;
    t = typeof value;
    switch (t) {
    case "object" :
    if(value==null){
    return null;
    }
    break;
    case "string" :
    return _string(value);
    case "number" :
    return isFinite(value) ? value + EMPTY : NULL;
    case "boolean" :
    return value + EMPTY;
    case "null" :
    return null;
    default :
    return undefined;
    }
    arr = value.length === undefined ? false : true;

    if (arr) { // Array
    for (i = value.length - 1; i >= 0; --i) {
    a[i] = serialize(value, i) || NULL;
    }
    }
    else { // Object
    i = 0;
    for (k in keys) {
    if (keys.hasOwnProperty(k)) {
    v = serialize(value, k);
    if (v) {
    a[i++] = _string(k) + colon + v;
    }
    }
    }
    }

    stack.pop();
    if (space && a.length) {

    return arr
    ? "[" + _indent(a.join(COMMA_CR), space) + "\n]"
    : "{\n" + _indent(a.join(COMMA_CR), space) + "\n}";
    }
    else {
    return arr ? "[" + a.join(COMMA) + "]" : "{" + a.join(COMMA)
    + "}";
    }
    }
    return serialize({
    "" : object
    }, "");
    }

    ReplyDelete
  17. I don't know why, but I found only one really working solution for form with inputs with names like name="some[sub][sub][sub][name]". There is it: http://jupiterjs.com/news/convert-form-elements-to-javascript-object-literals-with-jquery-formparams-plugin

    ReplyDelete
  18. Has anyone mentioned this link? pretty cool and as easy as $('#myform').formParams();

    ReplyDelete
  19. Tobias's solution above is the correct one, however, as commenter @macek pointed out, it does not handle inputs of type foo[bar] and split them into sub-objects.

    This is a PHP only feature, but I still find it very useful to be able to generate the same structure in javascript.

    I simply modified Tobias's code above, so all credit goes to him. This can probably be made cleaner, but I just whipped it up in five minutes and thought it might be useful.

    It does not handle multidimensional arrays or numerically indexed arrays at this time. IE, it will only work with names foo[bar] and not foo[]. I will update the snippet here if I make those changes later.

    jQuery.fn.serializeObjectPHP = function()
    {
    var o = {};
    var re = /^(.+)\[(.*)\]$/;
    var a = this.serializeArray();
    var n;
    jQuery.each(a, function() {
    var name = this.name;
    if((n = re.exec(this.name)) && n[2]) {
    if (o[n[1]] === undefined) {
    o[n[1]] = {};
    o[n[1]][n[2]] = this.value || '';
    } else if (o[n[1]][n[2]] === undefined) {
    o[n[1]][n[2]] = this.value || '';
    } else {
    if(!o[n[1]][n[2]].push) {
    o[n[1]][n[2]] = [ o[n[1]][n[2]] ];
    }
    o[n[1]][n[2]].push(this.value || '');
    }
    } else {
    if(n && !n[2]) { name = n[1]; }
    if (o[name] !== undefined) {
    if (!o[name].push) {
    o[name] = [o[name]];
    }
    o[name].push(this.value || '');
    } else {
    o[name] = this.value || '';
    }
    }
    });
    return o;
    };

    ReplyDelete