Atais Atais - 2 months ago 38
Scala Question

Scala Play Framework image upload with Angular ng-file-upload

I am using

Angular ng-file-upload
(https://github.com/danialfarid/ng-file-upload) on the frontend to manage the file upload process.

Unfortunately, form contains a complex object with multiple files. Using the
MultipartFormData
(https://www.playframework.com/documentation/2.5.x/ScalaBodyParsers) on the server side I have successfully decomposed the uploaded content and can read it from the
request.body
.

Now, to my surprise, I do not have a simple
Json Objects
but rather a strangely formed datatype, described on the
ng-file-upload
website as:


(...) server implementations expecting nested data object keys in .key or [key] format.
Example: data: {rec: {name: 'N', pic: file}} sent as: rec[name] -> N, rec[pic] -> file

data: {rec: {name: 'N', pic: file}, objectKey: '.k'} sent as: rec.name -> N, rec.pic -> file


So far I have managed to bring all the data to a common
MultipartFormData.Part
type, using the
DataPart
and
FilePart
like this:

val opts = body.dataParts.map {
case (key, values) => DataPart(key, values.head)
}

val parts = opts ++ body.files


So I am now left with a quite unfortunate
Iterable[Part]
:

0 = {MultipartFormData$DataPart@86271} "DataPart(arabic[active],false)"
1 = {MultipartFormData$DataPart@86273} "DataPart(english[active],true)"
2 = {MultipartFormData$DataPart@86277} "DataPart(english[url],2132132132)"
...
7 = {MultipartFormData$FilePart@76473} "FilePart(english[image],fb_icon_325x325.png,Some(image/png),TemporaryFile(/tmp/playtemp5909927824995768544/multipartBody8348573128070542611asTemporaryFile))"


Each object
name
contains the
key
of it's Json structure and its according
value
. Now instead of
key[level1][level2]
I would like to parse it to objects, in my case:

case class PcBanner(english: PcBanners, arabic: PcBanners, kurdish: PcBanners)
case class PcBanners(active: Boolean, url: Option[String], image: Option[String])`


I hope you got the idea.

The question

I know I could try to parse the
name
strings trying to fit it to objects, but I believe I made a mistake someway in the middle.
Is there a way to parse this structure into the objects, using field names as a reference? Any build in Play functions or alike?

Thanks for help!

Answer

As I stated in the title my case was to send images. As you would expect, I am also presenting a preview and the files currently saved in the database.

Considering all pros and cons I have decided to send all the data in JSON format, both ways. Meaning that the images are encoded and sent along in JSON structure.

The biggest advantage (and the one that convinced me) of this approach is that on the server side I was able to use the build-in JSON parser. Dealing with raw MultipartFormData is much more difficult and if you need to use it I would stick to @danial comment:

No have the file sent separately like this
{file: file, otherData: JSON.stringify(myData)}

This would allow to use the JSON parser for most of the content and only require some separate work with files.

My solution

If anyone would like to use similar approach to mine I present my answer. On the front-end side I have decided used ng-file-upload library. Binding it to HTML component with ngf-select with ngf-drop which enables the component:

<div ngf-drop ngf-select
     ng-model="image"
     ngf-change="parse($files, $file, $newFiles, $duplicateFiles, $invalidFiles, $event)"
     ngf-accept="'image/*'"
     ngf-resize="{width: {{width}}, height: {{height}}, quality: 1.0, restoreExif: false}">

    <img data-ng-src="{{image}}">
</div>

Inside the upload tag I put the image preview. This works flawlessly. Image is though displayed from base64 string, using data-ng-src tag. Behind the scenes, parse method encodes the file using ng-file-upload Upload.base64DataUrl service.

$scope.parse = function ($files, $file) {
    Upload.base64DataUrl($file).then(function (url) {
        $scope.image = url;
    });
};

Putting together all the above gave me the output data object:

{  
   "english":{  
      "active":true,
      "url":"http://google.com",
      "image":"data:image/png;base64,..."
   },
   "arabic":{  
      "active":true,
      "url":"http://google.com",
      "image":"data:image/png;base64,..."
   },
   "kurdish":{  
      "active":true,
      "url":"http://google.com",
      "image":"data:image/png;base64,..."
   }
}

Which can be send using regular Angular $http service or Upload.http from ng-file-upload. On the server side the JSON matches the prepared case class and is parsed with build-in Jackson parser, allowing for easy object manipulation.

Note that incoming images data strings contain data:image/png;base64, prefix and also should be send back with it for proper image display.

Enjoy!

Comments