javascriptarraysreducekey-valueform-data

How to create object-based form-data when there are multiple, equally named form-elements?


I'm trying to write a form_to_json serialization function in vanilla JS in order to create valid JSON.

But I'm stuck on parsing multiple inputs, all featuring the same name.

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.


Solution

  • 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 list 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="picked-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>