Search code examples
javascripthtmlformsform-data

How to get a key/value data set from a HTML form


I'm simply looking for a way to get all the values from a <form>.

I searched the Web for a while, stumbling across FormData, which seems quite what I'm looking for.

However its API is not available on any browser, so I need an alternative.


What I need in my specific case is an object of key/value pairs. For example:

<form>
  <input type="text" name="firstname" value="John" />
  <input type="text" name="surname" value="doe" />
  <input type="email" name="email" value="" />
  <input type="radio" name="gender" value="male" />
  <input type="radio" name="gender" value="female" />
</form>

The object should be:

{
  firstname: "John",
  surname: "doe",
  email: "",
  gender: ""
}

Edit: The above is just an example, it should work not only with <input> but also with the other tags (e.g. <select>, <textarea> and so on... even <input type="file"> should be supported).


Solution

  • When I originally wrote this answer FormData was not widely supported (and this was called out explicitly in the question). Now that it's been 6 years, FormData has excellent cross-browser support.

    Because of this, I highly recommend using FormData directly to access data or serialize data to the server.

    Jake Archibald has an excellent post describing FormData (and URLSearchParams) in depth, which I won't attempt to reproduce here, however I will include a few snippets that are relevant:

    You can populate FormData state directly:

    const formData = new FormData();
    formData.set('foo', 'bar');
    formData.set('hello', 'world');
    

    ...you can read an HTML form directly as FormData:

    const formElement = document.querySelector('form');
    const formData = new FormData(formElement);
    console.log(formData.get('username'));
    

    ...you can use FormData directly as a fetch body:

    const formData = new FormData();
    formData.set('foo', 'bar');
    formData.set('hello', 'world');
    
    fetch(url, {
      method: 'POST',
      body: formData,
    });
    

    I recommend reading Jake's post in full and using the APIs that come with the browser over adding more code to build a less-resilient version of the same thing.


    My original post preserved for posterity's sake:

    Without a strong definition of what should happen with edge cases, and what level of browser support is required, it's difficult to give a single perfect answer to the question.

    There are a lot of form behaviors that are easy to miss, which is why I recommend using a well-maintained function from a library, such as jQuery's serializeArray():

    $('form').serializeArray();
    

    I understand that there's a big push recently to move away from needlessly including jQuery, so for those who are interested in a vanilla JS solution serializeArray just won't do.

    The next difficulty comes from determining what level of browser support is required. HTMLFormElement.elements significantly simplifies a serialization implementation, and selecting the form-associated elements without it is quite a pain.

    Consider:

    <form id="example">...</form>
    <input type="text" form="example" name="lorem" value="ipsum"/>
    

    A conforming implementation needs to include the input element. I will assume that I can use it, and leave polyfilling it as an exercise to the reader.

    After that it'd unclear how <input type="file"/> should be supported. I'm not keen on needlessly serializing file elements into a string, so I've made the assumption that the serialization will be of the input's name and value, even though the value is practically useless.

    Lastly, an input structure of:

    {
        'input name': 'value',
        'textarea name': 'value'
    }
    

    Is excessively naive as it doesn't account for <select multiple> elements, or cases where two inputs have the same name. I've made the assumption that the input would be better as:

    [
        {
            name: 'input name',
            value: 'value'
        },
        {
            name: 'textarea name',
            value: 'value'
        }
    ]
    

    ...and again leave transforming this into a different structure as an exercise for the reader.


    Give me teh codez already!

    var serialize = (function (slice) {
        return function (form) {
            //no form, no serialization
            if (form == null)
                return null;
    
            //get the form elements and convert to an array
            return slice.call(form.elements)
                .filter(function (element) {
                    //remove disabled elements
                    return !element.disabled;
                }).filter(function (element) {
                    //remove unchecked checkboxes and radio buttons
                    return !/^input$/i.test(element.tagName) || !/^(?:checkbox|radio)$/i.test(element.type) || element.checked;
                }).filter(function (element) {
                    //remove <select multiple> elements with no values selected
                    return !/^select$/i.test(element.tagName) || element.selectedOptions.length > 0;
                }).map(function (element) {
                    switch (element.tagName.toLowerCase()) {
                        case 'checkbox':
                        case 'radio':
                            return {
                                name: element.name,
                                value: element.value === null ? 'on' : element.value
                            };
                        case 'select':
                            if (element.multiple) {
                                return {
                                    name: element.name,
                                    value: slice.call(element.selectedOptions)
                                        .map(function (option) {
                                            return option.value;
                                        })
                                };
                            }
                            return {
                                name: element.name,
                                value: element.value
                            };
                        default:
                            return {
                                name: element.name,
                                value: element.value || ''
                            };
                    }
                });
        }
    }(Array.prototype.slice));