Skip to main content
Baldur Bjarnason

FormData to Object

Baldur Bjarnason

The simplest method for serialising a form to an object I’ve found is:

const formObject = Object.fromEntries(new FormData(form));

This does not handle multiple entries of the same name at all, which can happen quite easier with <select multiple> for example.

This works for that (from this StackOverflow answer):

const formObject = Object.fromEntries(
	[...formData.keys()].map(key => [key, formData.getAll(key).length > 1 ? formData.getAll(key) : formData.get(key)])
)

It maps over the entry names, checks to see if the entry name has multiple values associated with it. If so, it fetches all of those values. Otherwise, it fetches just one key.

But this feels a bit like it’s trying too hard to be clever and do everything in one go.

This approach feels a bit clearer and drops Blob values:

const formObject = {};
for (const [key, value] of formData) {
	// If the property is defined, that means this key has multiple values associated with it
	if (formObject[key]) {
		formObject[key] = [].concat(formObject[key], value);
	// Don't want to add blobs to an object to be serialised as JSON  
  } else if (!(value instanceof Blob)) {
		formObject[key] = value;
  }
}

It’s longer in lines of code, but effectively of a similar complexity as the map solution, just makes it more obvious to the reader what’s going on. Wrap it in a helper function and you’re done. You could try to “optimise” it by checking the value with Array.isArray and using push instead of concat, but for most forms the difference is unlikely to be even measurable without turning cross-origin isolation on.

The question is what should you do for numerical values? Since the user can type numbers into string fields and those fields should continue to be strings, we can’t just try to parse all the numbers. But if we know in advance which input names are supposed to be numerical, we can use that as a guide.

function formDataToObject (formData, numberFields = []) {
	const formObject = {};
	for (const [key, value] of formData) {
		if (formObject[key]) {
			formObject[key] = [].concat(formObject[key], value);
			// Don't want to add blobs to an object to be serialised as JSON  
		} else if (!(value instanceof Blob)) {
			formObject[key] = numberFields.includes(key) ? parseFloat(value) : value;
		}
	}
}

We use parseFloat because, technically, all numbers in JS are floats (or, even more technically, it doesn’t make a distinction between floats and integers) – parseFloat("32") === parseInt("32") – but parseInt will drop the decimal parts if provided.

This will result in NaN if the numerical field returns a non-number, but that’s why you do form validation before you serialise to FormData.

We often overemphasise one-liner solutions when we talk about code. Functions are a concept that exist for a reason. Instead of using the same, flawed, one line solution everywhere, it’s usually better to have a slightly longer, but more sensibly designed, solution that’s packaged into a reusable function.