9

I know how to create endpoints that are handling files using MediaType.MULTIPART_FORM_DATA and @FormDataParam("file") FormDataBodyPart bodyPart, but I was wondering if I can also have JSON data along that request? Something like:

    @POST
    @Path("somepath")
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    public Response uploadFileAndJSON(@RequestBody SomeModel someModel,
                                      @FormDataParam("file") FormDataBodyPart bodyPart) { 
         return null;
    }

At the moment if I add some JSON data on the "raw" tab on the following Postman request I'm getting HTTP 415 Unsupported Media Type probably because I specified that I consume MULTIPART_FORM_DATA but I'm also using @RequestBody which is looking for JSON content which is APPLICATION_JSON. So how can I have JSON data and a file handled in the same request? I know that it's possible to do that in two requests, I just want to do it in one if possible?

enter image description here

Anton Belev
  • 11,963
  • 22
  • 70
  • 111
  • Please look at the below link for ur answer. http://stackoverflow.com/questions/4083702/posting-a-file-and-data-to-restful-webservice-as-json?rq=1 – spm Oct 16 '15 at 17:19

2 Answers2

7

Why are you using both Spring and Jersey annotations? You should stick to using the annotations meant for the framework. Since you are using Jersey, should stick to its its annotations.

So here are the things to consider about your current code and environment.

  1. There can't be two separate bodies. With your code, that's what it appears you expect to happen.
  2. You can though put the JSON as part of the multi-part body. For that you should also annotate the SomeModel with the Jersey @FormDataParam

    @POST
    @Path("somepath")
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    public Response uploadFileAndJSON(
             @FormDataParam("model") SomeModel someModel,
             @FormDataParam("file") FormDataBodyPart bodyPart) { 
    }
    
  3. In the Jersey configuration, you need to make sure to register the MultiPartFeature. If you don't the body won't be able to be deserialized, and you will get exceptions and error responses.

  4. Now the Postman problem. You can see similar problem here. The problem was that the Content-Type was not set for the JSON body part. For example the body might look something like

    --AaB03x
    Content-Disposition: form-data; name="model"
    
    {"some":"model", "data":"blah"}
    --AaB03x
    Content-Disposition: form-data; name="file"; filename="file1.txt"
    Content-Type: text/plain
    
    ... contents of file1.txt ...
    --AaB03x--
    

    You can actually see the body, if you hit the Preview button in Postman. The problem is that there is no Content-Type for the "model" part, as you can see in the "file" part. This happens because you can't set individual parts' Content-Type in Postman. The one that you will see will be discovered from the file extension. For example a .txt file will make Postman set the Content-Type to text/plain and a .png file to image/png.

    If you look in the link above, I proposed maybe you could use a .json file instead of typing in the data. Of course that was just a theory. I didn't actually test it.

    In any case, the Content-Type must be set in order for Jersey to be able to know to deserialize it as JSON. If the .json file extension theory doesn't pan out, then you can use a different client, like cURL, which I showed an example in the link, or you can use the Jersey client to test, as seen here.

  5. Don't set the Content-Type header to multipart/form-data in Postman. It sets it for you when you use the form-data. I just saw a post where someone said there is bug when you set the header. Can't find the post now, and not something I've confirmed, but I'd just leave it out.


UPDATE

So the OP was able to find a way to set the Content-Type: application/json to the "model" part. But it is sometimes the case where with a Javascript client, you are not able to set it. So there will be no Content-Type. If this is the case, Jersey will not be able to deserialize the JSON, as it has no idea that it is actually JSON being sent. If you absolutely can't or have no idea how to set the Content-Type for individual parts, you could resort to doing the following.

@POST
@Path("somepath")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadFileAndJSON(@FormDataParam("model") FormDataBodyPart jsonPart,
                                  @FormDataParam("file") FormDataBodyPart bodyPart) { 
     jsonPart.setMediaType(MediaType.APPLICATION_JSON_TYPE);
     SomeModel model = jsonPart.getValueAs(SomeModel.class);
}
Community
  • 1
  • 1
Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
  • http://stackoverflow.com/questions/29406331/org-glassfish-jersey-upload-file-with-formdatacontentdisposition/29406656#29406656 I used that in order to register the MultiPartFeature in order to be able to do @FormDataParam("model") SomeModel someModel. Still I get the model as null. If I change the type to String I'm able to get it as a String thought. Any ideas what could I have missed? – Anton Belev Oct 20 '15 at 10:06
  • That will happen if you don't set the `Content-Type` to `application/json` for the "model" part. That was also explained in my answer and the linked answer. – Paul Samsotha Oct 20 '15 at 10:17
  • My theory was that instead of typing in the JSON, try to use add a file with a `.json` extension. It _might_ set the `Content-Type` to `application/json`. I not sure. It is just a theory. I can't test because when I updated my chrome, somehow my Postman got deleted. I tried to install it again but it's a new version that I despise so I stopped using Postman. If that doesn't work then you may need to test with a different client. With cURL you can set individual parts' content types. Or with the Jersey client, the link has an example. – Paul Samsotha Oct 20 '15 at 10:27
  • The thing is that not only Postman sends this part of the request without proper Content-type but also the client of my endpoint sends that too. I will have a look how to add it to the client. – Anton Belev Oct 20 '15 at 10:33
  • If I cannot serialize it with Jersey I will just parse it with Gson (the least desirable case) – Anton Belev Oct 20 '15 at 10:34
  • 1
    Try this. Instead of `SomeModel` as the method param, use `FormDataBodyPart`. Then do `bodyPart.setMediaType(MediaType.APPLICATION_JSON_TYPE)`, then `SomeModel model = bodyPart.getValueAs(SomeModel.class)`. Just as much work as if you were to use Gson, but at least you don't need the Gson dependency – Paul Samsotha Oct 20 '15 at 13:03
  • 1
    I ended up altering the client to add the Content-Type application.json to the model part of the request and I then I was able to serialize it with Jersey : ) Thanks for your help, much appreciated! – Anton Belev Oct 20 '15 at 13:29
  • I found most of API tools (Postman, Swagger) cannot send MultiPart request with content-type of JSON. So there are two type of method to solve: one is the solution in Update, the other one is use Jackson to mapper string to object. – Xin Meng Jan 27 '17 at 12:24
1

Yes, you can get that as multipart form data.

you get like this in angularjs:

$scope.uploadFile = function () {
                var file = $scope.selectedFile[0];
                $scope.upload = $upload.upload({
                    url: 'api/upload',
                    method: 'POST',
                    data: angular.toJson($scope.model),
                    file: file
                }).progress(function (evt) {
                    $scope.uploadProgress = parseInt(100.0 * evt.loaded / evt.total, 10);
                }).success(function (data) {
                    //do something
                });
            };

            $scope.onFileSelect = function ($files) {
                $scope.uploadProgress = 0;
                $scope.selectedFile = $files;
            };
public Response uploadFileAndJSON(@RequestParam("data") String data,
                                      @MultiPartFile("file")File file) { 
  you can data as form data and convert it 
  like you want to your object using Gson jar.       
 return null;
    }

Have a look at it for angularjs code: Angularjs how to upload multipart form data and a file?

https://puspendu.wordpress.com/2012/08/23/restful-webservice-file-upload-with-jersey/

Community
  • 1
  • 1
Kumaresan Perumal
  • 1,926
  • 2
  • 29
  • 35