Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

GitHub project repo

I've been working on this little function to convert an HTML form into a JSON Object having the same structure of the form. Basically it is intended to be useful in those situations where you let your user dynamically alter the structure of your form, for examle:

    <form name="myForm" data-detect="true">

        <label for="myForm">Form</label>

        <input name="myFirstName" placeholder="myFirstName"/>
        <input name="mySecondName" placeholder="mySecondName"/>
        <input name="myLastName" placeholder="myLastName"/>

        <fieldset name="myLibrary">

            <legend>Library</legend>

            <input name="myLibraryName" placeholder="myLibraryName"/>

            <select name="myLibraryGenre">
                <option value="SciFi">Sci-Fi</option>
                <option value="Horror">Horror</option>
                <option value="Manuals">Manuals</option>
                <option value="Comics">Comics</option>
            </select>

            <fieldset name="myBook">

                <legend>Book</legend>

                <input name="myBookTitle" placeholder="myBookTitle"/>
                <input name="myBookDate" type="date" placeholder="myBookDate"/>
                <input name="myBookEditor" placeholder="myBookEditor"/>
                <br/>
                <input name="myFavouriteQuote" placeholder="myFavouriteQuote"/>
                <input name="myFavouriteQuote" placeholder="myFavouriteQuote"/>
                <input name="myFavouriteQuote" placeholder="myFavouriteQuote"/>
                <input name="myFavouriteQuote" placeholder="myFavouriteQuote"/>

            </fieldset>

            <fieldset name="myBook">

                <legend>Book</legend>

                <input name="myBookTitle" placeholder="myBookTitle"/>
                <input name="myBookDate" type="date" placeholder="myBookDate"/>
                <input name="myBookEditor" placeholder="myBookEditor"/>
                <br/>
                <input name="myFavouriteQuote" placeholder="myFavouriteQuote"/>
                <input name="myFavouriteQuote" placeholder="myFavouriteQuote"/>
                <input name="myFavouriteQuote" placeholder="myFavouriteQuote"/>
                <input name="myFavouriteQuote" placeholder="myFavouriteQuote"/>

            </fieldset>

        </fieldset>

        <input type="submit" value="Submit me!">
    </form>

where user can add more favourite quotes, or add more books or even more libraries dynamically using jQuery to add form parts and assigning the new inputs names to be those shown in the above three example, i.e. a new fav. quote input text will have name="myFavouriteQuote" and so on.

Done this you want to grab your datas so you can send them over to a server scipt in a way that keeps the original data structure created within the form using fieldsets as in the example.

So a JSON rapresentation of the Object would be for example:

{

    "myFirstName":"Carlo",

    ...,

    "myBook":[

        {

            "myBookTitle":"some title"

            ...,

            "myFavouriteQuote":[

                {0:"quote1"},
                {1:"quote2"},
                ...

            ]

        },

        ...

    ]

}

This because the final goal is to send this object to a server side php script which will convert the correctly nested data array to an XML file representing the exact structure of the form (where the tag names will be the elements' 'name' attributes).

To do so I basically select the form with $("[data-detect]") and pass it as the node parameter to my toJSON function:

function toJSON(node){

    if($(node).children().length == 0) return $(node).val();

    var json = new Object();

    $(node).children("[name]").each(function(){

        name = $(this).attr('name');

        if($(node).children("[name="+name+"]").length > 1){

             if(!json[name]) json[name] = [];

             json[name].push(toJSON(this));

        }else if(($(this).children(':not(option)').length > 0)){

             json[name] = toJSON(this);

        }else{

             json[name] = $(this).val();    

        }           

    }); 

    return json;

}

so like :

myJSONDatas = toJSON($("[data-detect]"));

Is there any way I can improve my function? How could I modify this to handle combo and radio boxes?

EDIT

I rewrote the function as a jQuery plugin and improved it a bit:

$.fn.toJSON = function() {

    if(!this.children().length) return this.val();

    var json = new Object();

    this.children('[name]').each(function(){

         if($(this).siblings("[name="+$(this).attr('name')+"]").length){

            if(!json[$(this).attr('name')]) json[$(this).attr('name')] = [];

            json[$(this).attr('name')].push($(this).toJSON());

         }else if($(this).children('[name]').length){

            json[$(this).attr('name')] = $(this).toJSON();

         }else{

            json[$(this).attr('name')] = $(this).val(); 

         }          

    }); 

    return json;

};

now the call will be simply

myJSONForm = $("[data-detect]").toJSON();

EDIT 2

I added support for checkbox and radio input. Plus I replaced all those $(this).attr('name'); in code.

$.fn.toJSON = function() {

    if(!this.children().length) return this.val();

    var json = new Object();

    this.children('[name]').each(function(){

        var name = $(this).attr('name');
        var type = $(this).attr('type');

        if($(this).siblings("[name="+name+"]").length){

            if( type == 'checkbox' && !$(this).prop('checked')) return true;
            if( type == 'radio' && !$(this).prop('checked')) return true;

            if(!json[name]) json[name] = [];

            json[name].push($(this).toJSON());

        }else if($(this).children('[name]').length){

            json[name] = $(this).toJSON();

        }else{

            json[name] = $(this).val(); 

        }           

    }); 

    return json;

};

Is this really returning a JSON object? What is the difference with a regular JS Objects?

LAST EDIT

Here is my last edit since I think this is as concise as my brain can go. Basically I deleted that double this.children.length check:

$.fn.toJSO = function() {

    if(!this.children('[name]').length) return this.val();

    var jso = new Object();

    this.children('[name]').each(function(){

        var name = $(this).attr('name');
        var type = $(this).attr('type');

        if($(this).siblings("[name="+name+"]").length){

            if( type == 'checkbox' && !$(this).prop('checked')) return true;
            if( type == 'radio' && !$(this).prop('checked')) return true;

            if(!jso[name]) jso[name] = [];

            jso[name].push($(this).toJSO());

        }else{

            jso[name] = $(this).toJSO();

        }       

    }); 

    return jso;
};
share|improve this question
3  
Have you considered Knockout.js? I am working on an answer specifically to this question but modification to handle combos/radios/dates/tabular data/whatever else is at least non-trivial. –  Bill Barry Jul 9 '12 at 17:56
2  
Is it really JSON or just a JS object? By the way, your code formatting style is quite confusing and you're repeating "$(this).attr('name')" a lot. –  Quentin Pradet Jul 12 '12 at 9:06
2  
Don't forget to declare your variables with var or they will be global. –  RoToRa Jul 12 '12 at 13:30
1  
Is there any difference between JavaScript literal objects and what we call JSON objects? I honestly didn't see any difference between those, this article agrees: benalman.com/news/2010/03/theres-no-such-thing-as-a-json. I'm guess the big difference are functions in javascript? –  Alpha Jul 12 '12 at 18:03
1  
toJSON is correct. It's a flawed naming convention in ECMAScript. It returns the object to use when calling JSON.stringify: JSON.stringify({toJSON: function () {return someVar;}}) == JSON.stringify(someVar) –  Brian Nickel Jul 16 '12 at 19:58

2 Answers 2

up vote 2 down vote accepted

Here are a few enhancements that I made to your code.

1)

Cached references to this and $(this).children('[name]').

2)

Used a regular expression to check if the element type is a radio or checkbox.

3)

In this case,

if (!jso[name])
    jso[name] = [];

is the same as

jso[name] = jso[name] || [];

4)

var jso = new Object(); is the same as var jso = {};

5)

Negated the if condition to get rid of the return true.

if (type == 'radio' && !$(this).prop('checked')){
    return true;
}
//other stuff

becomes

if (type != 'radio' || $(this).prop('checked')){
    //other stuff
}

Final Result

$.fn.toJSO = function () {
    var obj = {},
        $kids = $(this).children('[name]');
    if (!$kids.length) {
        return $(this).val();
    }
    $kids.each(function () {
        var $el = $(this),
            name = $el.attr('name');
        if ($el.siblings("[name=" + name + "]").length) {
            if (!/radio|checkbox/i.test($el.attr('type')) || $el.prop('checked')) {
                obj[name] = obj[name] || [];
                obj[name].push($el.toJSO());
            }
        } else {
            obj[name] = $el.toJSO();
        }
    });
    return obj;
};

Demo: http://jsfiddle.net/x4DjZ/

share|improve this answer
    
Thanks! those are cool enhancements indeed! –  Onheiron Sep 6 '12 at 6:41

This is a re-implementation of jQuery's build-in serialize() function. In general I would recommend using the built-in function since the jQuery developers will keep it updated for browser compatibility and use with other jQuery functions.

share|improve this answer
    
I actually looked at .serialize a little more and it doesn't do exactly what I need. It seems to convert forms in "key=value&key2=value..." strings witho no structure at all (i know you can set a name like array[i] to make .serialize generate structured datas, but this isn't good if you want your user to just compose forms AS IF they were an XML), also it doesn't detect fieldsets and this doesn't help building the XML structures (like the <myBook> tag which is a mere container). Can you suggest me some workarounds for these problems? thanks –  Onheiron Jul 17 '12 at 8:22
1  
.serialize() will only include the forms' user input elements. So you're right that it'll ignore extra info like fieldsets. Since all of your processing must be done client side I think your custom solution (last edit) is the way to go. I haven't come across a jQuery plugin that will do anything much closer to what you need. Sorry! –  Matt S Jul 18 '12 at 14:16
    
thanks anyways! –  Onheiron Jul 18 '12 at 14:19

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.