Skip to content

Instantly share code, notes, and snippets.

@ghinda
Last active March 30, 2024 18:51
Show Gist options
  • Save ghinda/8442a57f22099bdb2e34 to your computer and use it in GitHub Desktop.
Save ghinda/8442a57f22099bdb2e34 to your computer and use it in GitHub Desktop.
JavaScript Object to FormData, with support for nested objects, arrays and File objects. Includes Angular.js usage.
// takes a {} object and returns a FormData object
var objectToFormData = function(obj, form, namespace) {
var fd = form || new FormData();
var formKey;
for(var property in obj) {
if(obj.hasOwnProperty(property)) {
if(namespace) {
formKey = namespace + '[' + property + ']';
} else {
formKey = property;
}
// if the property is an object, but not a File,
// use recursivity.
if(typeof obj[property] === 'object' && !(obj[property] instanceof File)) {
objectToFormData(obj[property], fd, property);
} else {
// if it's a string or a File object
fd.append(formKey, obj[property]);
}
}
}
return fd;
};
// usage example
var z = objectToFormData({
obj: {
prop: 'property value'
},
arr: [
'one',
'two',
'three',
new File([''], '')
],
file: new File([''], '')
});
var xhr = new XMLHttpRequest;
xhr.open('POST', '/', true);
xhr.send(z);
// usage for Angular.js
// wrap object to formdata method,
// to use it as a transform with angular's http.
var formDataTransform = function(data, headersGetter) {
// we need to set Content-Type to undefined,
// to make the browser set it to multipart/form-data
// and fill in the correct *boundary*.
// setting Content-Type to multipart/form-data manually
// will fail to fill in the boundary parameter of the request.
headersGetter()['Content-Type'] = undefined;
return objectToFormData(data);
};
$http({
method: 'POST',
url: '/',
transformRequest: formDataTransform,
data: { your_object: {} }
})
.success(function(res) {});
@albertovincenzi
Copy link

Hi,
I figured out that the script removes all properties with null value. Why? Because typeof null is object so you get in the recursive loop and then null value will disappear. What do you think about checking obj[property] !== null ?

let fd = form || new FormData();
    let formKey;
    for (let property in obj) {
      if (obj.hasOwnProperty(property)) {
        if (namespace) {
          formKey = namespace + "[" + property + "]";
        } else {
          formKey = property;
        }
        if (
          typeof obj[property] === "object" &&
          !(obj[property] instanceof File) &&
          obj[property] !== null
        ) {
          this.objectToFormData(obj[property], fd, property);
        } else {
          if (obj[property] !== false) {
            fd.append(formKey, obj[property]);
          }
        }
      }
    }
    return fd;

@develforever
Copy link

Namespace is lost. This could fix it:
objectToFormData(obj[property], fd, namespace+"["+ property+"]");

@IvsonEmidio
Copy link

IvsonEmidio commented Aug 16, 2022

Update for Typescript 2022.
Edit: Date support removed.

function parseGenericObject<T>(
   object: T,
   form?: FormData,
   namespace?: string
 ): FormData {
   const formData = form || new FormData()
   for (const property in object) {
     const isPropertyExist = property in object

     if (!isPropertyExist) {
       continue
     }

     const contextProperty = object[property]
     const formKey = namespace ? `${namespace}[${property}]` : property

     if (
       typeof contextProperty === 'object' &&
       !(contextProperty instanceof File)
     ) {
       parseGenericObject<any>(contextProperty, formData, formKey)
     } else {
       formData.append(formKey, String(contextProperty))
     }
   }
   return formData
 }

@Ou7law007
Copy link

This does not work. It obviously is not nesting anything.

The server receives: {'p1[p1]' (all one key): val} instead of {'p1': {'p2': val}}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment