skip to Main Content

EDIT: not sure if knowing my back end is helpful or not, but I’ll be sending to Flask/Python.

trying to write a "form_to_json" serialization function in vanilla js to take a form ID and post that as valid json.

I’m stuck on parsing multiple inputs with the same names.

Here is my current function, note the comment where I’m stuck.

// gather form elements and serialize inputs
    function form_to_json(form_id) {
        console.log(`Getting form with ID ${form_id}`);
        let obj_form = document.getElementById(form_id);

        // Create an object
        var obj = {};
        
        // Loop through each field in the form
        Array.prototype.slice.call(obj_form.elements).forEach(function (field) {
            // Skip some fields we don't need
            if (!field.name || field.disabled || ['file', 'reset', 'submit', 'button'].indexOf(field.type) > -1) return;
            
            // Handle multi-select fields
            if (field.type === 'select-multiple') {
                // Create an array of selected values
                var options = [];
                
                // Loop through the options and add selected ones
                Array.prototype.slice.call(field.options).forEach(function (option) {
                    if (!option.selected) return;
                    options.push(option.value);
                });
                
                // If there are any selection options, add them to the object
                if (options.length) {
                    obj[field.name] = options;
                }
                
                return;
            }
            
            // If it's a checkbox or radio button and it's not checked, skip it
            if (['checkbox', 'radio'].indexOf(field.type) > -1 && !field.checked) return;

            console.log(`${field.name}: ${field.value}`);
            
            // Add the value to the object
            if (field.name in obj) {
                // NOT SURE WHAT TO DO HERE
                obj[field.name].push(field.value);
            } else {
                obj[field.name] = field.value;
            }
        });
        
        // Return the object
        return obj;
    }

Here is a screenshot showing a visual of how the inputs are created by the end user.

enter image description here

With my current function code, I’m obviously only getting the last set of values for each redundant field name.

The front end allows user to dynamically add "criteria" rows (basically a set of those 3 inputs all named identically between each row).

My attempt was to check if the key already existed in the object, and if so, /do something/ but I can’t work out what that something is.

2

Answers


  1. You can modify your if statement to convert the existing value into an array if it’s not one already. The if would look like this:

    // Add the value to the object
    if (field.name in obj) {
        if (!Array.isArray(obj[field.name])) {
          obj[field.name] = [obj[field.name]];
        }
        obj[field.name].push(field.value);
    } else {
        obj[field.name] = field.value;
    }
    

    Below the working example with the modified code:

    const form = document.getElementById('myform');
    form.addEventListener('submit', function (e) {
      e.preventDefault();
      
      const serialized = form_to_json('myform');
      console.log(serialized);
    });
    
    // gather form elements and serialize inputs
        function form_to_json(form_id) {
            console.log(`Getting form with ID ${form_id}`);
            let obj_form = document.getElementById(form_id);
    
            // Create an object
            var obj = {};
            
            // Loop through each field in the form
            Array.prototype.slice.call(obj_form.elements).forEach(function (field) {
                // Skip some fields we don't need
                if (!field.name || field.disabled || ['file', 'reset', 'submit', 'button'].indexOf(field.type) > -1) return;
                
                // Handle multi-select fields
                if (field.type === 'select-multiple') {
                    // Create an array of selected values
                    var options = [];
                    
                    // Loop through the options and add selected ones
                    Array.prototype.slice.call(field.options).forEach(function (option) {
                        if (!option.selected) return;
                        options.push(option.value);
                    });
                    
                    // If there are any selection options, add them to the object
                    if (options.length) {
                        obj[field.name] = options;
                    }
                    
                    return;
                }
                
                // If it's a checkbox or radio button and it's not checked, skip it
                if (['checkbox', 'radio'].indexOf(field.type) > -1 && !field.checked) return;
    
                console.log(`${field.name}: ${field.value}`);
                
                // Add the value to the object
                if (field.name in obj) {
                    if (!Array.isArray(obj[field.name])) {
                      obj[field.name] = [obj[field.name]];
                    }
                    obj[field.name].push(field.value);
                } else {
                    obj[field.name] = field.value;
                }
            });
            
            // Return the object
            return obj;
        }
    <form id='myform'>
    
    <input name='crit' />
    <input name='crit' />
    <input name='crit' />
    <input name='otherfied' />
    
    <input type='submit' value='submit' />
    </form>
    Login or Signup to reply.
  2. Regardless of the fact that the OP actually does not want to create JSON (which is a string-based data-format), but wants to create unified form-data in a custom way, any solution in first place should have been based at the usage of the FormData Web API, a form-data’s entries and a single reduce task.

    A FormData instance provides access to all of a form’s relevant control/element-values, actually already filtered in the way the OP is looking for, which only leaves the task of collecting the values of equally named field-names into arrays, thus reducing the entries array (key-value pairs) into an object based data-structure.

    The entire code becomes shorter and better readable.

    function getUnifiedFormData(elmForm) {
      return [
        ...new FormData(elmForm).entries()
      ]
      .reduce((result, [key, value]) => {
    
        if (Object.hasOwn(result, key)) {
          if (Array.isArray(result[key])) {
    
            result[key].push(value);
          } else {
            result[key] = [result[key], value];
          }
        } else {
          result[key] = value;
        }
        return result;
    
      }, {});
    }
    document
      .forms['my-form']
      .addEventListener('submit', evt => {
        evt.preventDefault();
    
        const data = getUnifiedFormData(evt.currentTarget);
    
        console.clear();
        console.log({ data });
      });
    body { margin: 0; }
    form { width: 29%; }
    label { float: left; margin: 2px 4px; }
    select { clear: left; margin: 2px 0; padding: 2px 10px; }
    [type="text"] { margin: 4px 0; }
    [type="text"], select, label > span { display: block; }
    .as-console-wrapper { left: auto!important; width: 70%; min-height: 100%; }
    <form id='my-form'>
      <input type="text" name='text-values' />
      <input type="text" name='text-values' />
      <input type="text" name='text-values' />
      
      <label>
        <span>red</span>
        <input type="checkbox" name="selected-colors" value="red" />
      </label>
      <label>
        <span>yellow</span>
        <input type="checkbox" name="selected-colors" value="yellow" />
      </label>
      <label>
        <span>green</span>
        <input type="checkbox" name="selected-colors" value="green" />
      </label>
    
      <select name="fruits" multiple>
        <option>Apple</option>
        <option>Orange</option>
        <option>Banana</option>
      </select>
      
      <input type="text" name='other' value="other value" disabled />
    
      <input type='submit' name="submit" value="submit" />
      <input type='reset' name="reset" value="reset" />
      <input type='button' name="button" value="button" />
    </form>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search