Composing multipart/form-data with a different Content-Type on each parts with Javascript (or Angular)
Asked Answered
P

4

63

Wrong question asked, see my update below

I need to integrate my AngularJS Project with an existing RESTful API. These API consume POST request which upload a file, and also submit the form data in a request. Unfortunately, one of the form input requires to be in Content-Type: Application/json.

After search on the web, I could only POST with Content-Type: multipart/form-data in which each of the parts does not have a specific MIME. How can I compose my multipart/form-data with a different MIME for each parts in Javascript?

POST /api/v1/inventory
Host: localhost:8000
Origin: http://localhost:9000
Content-Type: multipart/form-data; boundary=------border

------border
Content-Disposition: form-data; name="owner"

john doe
------border
Content-Disposition: form-data; name="image"; filename="mybook.png"
Content-Type: image/png


------border
Content-Disposition: form-data; name="items"
Content-Type: application/json

{"name": "Book", "quantity": "12"}
------border--

Relevant References:

  1. https://developer.mozilla.org/en-US/docs/Web/Guide/Using_FormData_Objects
  2. REST - HTTP Post Multipart with JSON
  3. http://code.activestate.com/recipes/578846-composing-a-postable-http-request-with-multipartfo/
  4. application/x-www-form-urlencoded or multipart/form-data?
  5. https://mcmap.net/q/210368/-rest-http-post-multipart-with-json

Update

Apologize for asking a wrong question. The original problem is that, I can see the server calling the logic something like,

func POST(req):
     owner = req.owner // This is string
     image = req.image // This is file object
     itemQuantity = req.items.quantity // Items is an object with attribute quantity
     itemName = req.items.name // Items is an object with attribute name

I have also managed to figure out how to submit such a post request. I will post my answer below.

Once again sorry for asking a wrong question.

Penult answered 2/7, 2014 at 15:28 Comment(4)
The API is currently in used with the mobile apps. If the mobile apps could generate such request, I believe that web browser should be able to compose these request also.Penult
Reference 5. show the spec that we should be able to compose this kind of request.Penult
Not sure if I have to create a custom XMLHttpRequest from scratch for this? developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequestPenult
Should this question be closed as it is a wrong question.Penult
K
116

According to the documentation of FormData, you can append a field with a specific content type by using the Blob constructor:

var formData = new FormData();

formData.append('items', new Blob([JSON.stringify({
    name: "Book",
    quantity: "12"
})], {
    type: "application/json"
}));

After careful observation, it turns out that it will send the part as follows:

Content-Disposition: form-data; name="items"; filename="blob"
Content-Type: text/json

The only alternative, safe from building the whole request yourself is to pass a string value:

formData.append('items', '{"name": "Book", "quantity": "12"}');

This, unfortunately, doesn't set the Content-Type header.

Kick answered 2/7, 2014 at 15:34 Comment(10)
Although it set the Content-Type: 'application/json', it is still wrapped under a Blob. Hence, the API reject my request. gist.github.com/9gix/…Penult
Ah, think I've made a typo; please try again.Mammalogy
The result of my previous comment is based on the proper syntax of Blob.Penult
@Penult Did you try formData.append('items', JSON.stringify(...)) as well?Mammalogy
because i'm using angular, therefore this is what I use to convert into JSON angular.toJson() Which I believe does the same things. docs.angularjs.org/api/ng/function/angular.toJsonPenult
@Penult That should be the same; it seems that with FormData you either get to upload a file with a specific content type or just a string value (without specific content type).Mammalogy
The fact of data being sent with filename="blob" is not a problem. This Blob wrapper is perfectly working for me, with Angular 2 as front end and Spring 4 as backend.Plano
Yeap works with blob: var blob = new Blob([JSON.stringify(requestVo)], { type: "application/json" }) formData.append('data',blob);Laughing
Thank you! This was exactly what I needed!Tolson
How do I make this work in node.js, reading a file from the file-system? Blob is not available on node.Lordship
P
9

Mistake #1: I mistakenly assume that the items has to be a json, so that we can call its attribute.

Solution: To submit a multipart request that contain a file and an object like format is very simple.

form = new FormData();
form.append('items[name]', 'Book');
form.append('items[quantity]', 12);
form.append('image', imageFile);
form.append('owner', 'John Doe');

So thus the request header and body will looks something like this

POST /api/v1/inventory
Host: localhost:8000
Origin: http://localhost:9000
Content-Type: multipart/form-data; boundary=------border

------border
Content-Disposition: form-data; name="owner"

john doe
------border
Content-Disposition: form-data; name="image"; filename="mybook.png"
Content-Type: image/png


------border
Content-Disposition: form-data; name="items[name]"

Book
------border
Content-Disposition: form-data; name="items[quantity]"

12
------border--
Penult answered 3/7, 2014 at 16:37 Comment(1)
Ah, you saved my day. I also assumed json had to be in a single object like a regular post data! thanks.Vessel
B
7

Nothing would get this to work, until I set the Content-Type header to undefined. In my case I am posting a file and some json.

public uploadFile(code: string, file):angular.IHttpPromise<any>{
    var data = `{"query":"mutation FIRMSCORECARD_CALCULATE($code:String!){ FirmScorecardMutation{ BatchCalculate(Code:$code) }}","variables":{"code":"${code}"},"operationName":"FIRMSCORECARD_CALCULATE"}`;
    var formData = new FormData();
    formData.append('operations', data);
    formData.append('file', file, file.name);

    let config = {
        headers: {
            'Accept': 'application/json',
            'Content-Type': undefined
        }
    };
    let response = this.$http.post(this.graphqlUrl, formData, config);
    return response;
}
Balsamic answered 18/4, 2019 at 22:7 Comment(1)
Here is the only place where I found how to set up string variables in graphql request with javascript.Gheber
H
0

You can try this:

private void FillMultipartFormDataContent(object value, MultipartFormDataContent content,string basename)
{
    var properties = value.GetType().GetProperties();
    foreach (PropertyInfo property in properties)
    {
        var propertyObject= property.GetValue(value, null);
        if (propertyObject != null)
        { 
            var propertyObjectType= propertyObject?.GetType();
            if (propertyObjectType.Namespace!="System" && !propertyObjectType.IsEnum)
            {
                FillMultipartFormDataContent(propertyObject, content, $"{basename}.{property.Name}");
            }
            else
            {
                content.Add(new StringContent(propertyObject?.ToString()), $"{basename}.{property.Name}");
            }
        }

    }
}
Hyetology answered 3/7, 2024 at 7:5 Comment(1)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Oina

© 2022 - 2025 — McMap. All rights reserved.