Skip to the code
cf.

Serialize a form to an object

MediumDOM

Write serializeForm(form), which returns the form's current values as a plain object keyed by each control's name, ready to send as JSON.

Text-like inputs (text, email, password, number, hidden, and so on), textareas, and single-choice selects give their value as a string. A radio group gives the checked radio's value, and is left out when none is checked.

A checkbox that's the only control with its name gives true or false. Checkboxes that share a name give an array of the checked ones' values, in page order, which may be empty. A <select multiple> gives an array of the selected options' values, which may also be empty.

Leave out controls with no name, disabled controls (including those inside a disabled <fieldset>), buttons, and file inputs. Read what the user has entered now, not what the HTML started with.

serializeForm(document.getElementById("signup"));
// {
//   name: "Ada", email: "ada@example.com", password: "secret", plan: "pro",
//   newsletter: true, topics: ["js", "a11y"], country: "in",
//   languages: ["en", "hi"], bio: "Hello",
// }

What the tests check

  1. reads text inputs, textareas, and selects as strings
    const data = serializeForm(document.getElementById("signup"));
    expect(data.name).toBe("Ada");
    expect(data.email).toBe("ada@example.com");
    expect(data.password).toBe("secret");
    expect(data.country).toBe("in");
    expect(data.bio).toBe("Hello");
    
  2. uses the checked radio's value, or leaves the group out
    const form = document.getElementById("signup");
    expect(serializeForm(form).plan).toBe("pro");
    form.querySelector('[name="plan"][value="pro"]').checked = false;
    expect("plan" in serializeForm(form)).toBe(false);
    
  3. a lone checkbox gives true or false
    const form = document.getElementById("signup");
    expect(serializeForm(form).newsletter).toBe(true);
    fire(form.querySelector('[name="newsletter"]'), "click");
    expect(serializeForm(form).newsletter).toBe(false);
    
  4. checkboxes that share a name always give an array
    const form = document.getElementById("signup");
    expect(serializeForm(form).topics).toEqual(["js", "a11y"]);
    fire(form.querySelector('[value="js"]'), "click");
    fire(form.querySelector('[value="a11y"]'), "click");
    expect(serializeForm(form).topics).toEqual([]);
    fire(form.querySelector('[value="css"]'), "click");
    expect(serializeForm(form).topics).toEqual(["css"]);
    
  5. a multi-select always gives an array
    const form = document.getElementById("signup");
    const select = form.querySelector('[name="languages"]');
    expect(serializeForm(form).languages).toEqual(["en", "hi"]);
    select.options[0].selected = false;
    expect(serializeForm(form).languages).toEqual(["hi"]);
    select.options[1].selected = false;
    expect(serializeForm(form).languages).toEqual([]);
    
  6. reads what the user entered, not the HTML's starting values
    const form = document.getElementById("signup");
    const nameField = form.querySelector('[name="name"]');
    nameField.value = "Grace";
    fire(nameField, "input");
    const bio = form.querySelector('[name="bio"]');
    bio.value = "Bye";
    fire(bio, "input");
    const country = form.querySelector('[name="country"]');
    country.value = "uk";
    fire(country, "change");
    const data = serializeForm(form);
    expect(data.name).toBe("Grace");
    expect(data.bio).toBe("Bye");
    expect(data.country).toBe("uk");
    
  7. leaves out disabled, unnamed, button, and file controls
    const keys = Object.keys(serializeForm(document.getElementById("signup"))).sort();
    expect(keys).toEqual(["bio", "country", "email", "languages", "name", "newsletter", "password", "plan", "topics"]);
    
  8. returns the whole form as one object
    expect(serializeForm(document.getElementById("signup"))).toEqual({
      name: "Ada",
      email: "ada@example.com",
      password: "secret",
      plan: "pro",
      newsletter: true,
      topics: ["js", "a11y"],
      country: "in",
      languages: ["en", "hi"],
      bio: "Hello",
    });
    

Learn more

solution.jsSaved in this browser

Run tests runs every test on your code, here in your browser. The problem is solved when they all pass.

Ctrl + Enter runs the tests (⌘ + Enter on a Mac).