Karlom
Karlom

Reputation: 14834

How to get file posted from JSON in go gin?

I want to save image file posted by JSON.

Here is the struct of the post:

type Article struct {
    Title   string `json:"title"`
    Body string `json:"body"`
    File    []byte `json:"file"`
}

And the handler is :

   func PostHandler(c *gin.Context) {
        var err error
        var json Article
        err = c.BindJSON(&json)
        if err != nil {
            log.Panic(err)
        }

    //handle photo upload
        var filename string
        file, header, err := json.File  //assignment count mismatch: 3 = 1

        if err != nil {
            fmt.Println(err)
            filename = ""

        } else {
            data, err := ioutil.ReadAll(file)
            if err != nil {
                fmt.Println(err)
                return
            }

            filename = path.Join("media", +shared.RandString(5)+path.Ext(header.Filename))

            err = ioutil.WriteFile(filename, data, 0777)
            if err != nil {
                io.WriteString(w, err.Error())
                return
            }

        }
...

But I get

assignment count mismatch: 3 = 1

I copied the file handling part from a working multipart form handler which worked fine but apparently,

file, header, err := r.FormFile("uploadfile")

can not be translated into JSON handling.

I have looked at gin docs but could not find examples involving json file handling. So how can I fix this?

Upvotes: 9

Views: 16589

Answers (2)

tim-montague
tim-montague

Reputation: 17372

Using Gin to get an uploaded file

I think your question is "Using Gin, how do I get an uploaded file?". Most developers don't upload files with JSON encoding, which could be done but requires the file to be included as a base64 string (and increases the file size about 33%).

The common (and more efficient) practice is to upload the file using the "multipart/form-data" encoding. The code that others provided file, header, err := c.Request.FormFile("file") works, but that hijacks the underlining "net/http" package that Gin extends.

My recommendation is to use ShouldBind, but you can also use the FormFile or MultipartForm methods provided by the Gin package.

Examples below. Similar (but less detailed) explanations are also offered on the Gin page https://github.com/gin-gonic/gin#model-binding-and-validation and https://github.com/gin-gonic/gin#upload-files.


Upload one file


Clients

HTML

<form action="/upload" method="POST">
  <input type="file" name="file">
  <input type="submit">
</form>

Curl

curl -X POST http://localhost:8080/upload \
  -F "file=../path-to-file/file.zip" \
  -H "Content-Type: multipart/form-data"

Server

Go

package main

import (
    "github.com/gin-gonic/gin"
    "log"
    "net/http"
    "io/ioutil"
)

type Form struct {
    File *multipart.FileHeader `form:"file" binding:"required"`
}

func main() {
    router := gin.Default()

    // Set a lower memory limit for multipart forms (default is 32 MiB)
    // router.MaxMultipartMemory = 8 << 20  // 8 MiB

    router.POST("/upload", func(c *gin.Context) {

        // Using `ShouldBind`
        // --------------------
        var form Form
        _ := c.ShouldBind(&form)

        // Get raw file bytes - no reader method
        // openedFile, _ := form.File.Open()
        // file, _ := ioutil.ReadAll(openedFile)

        // Upload to disk
        // `form.File` has io.reader method
        // c.SaveUploadedFile(form.File, path)
        // --------------------

        // Using `FormFile`
        // --------------------
        // formFile, _ := c.FormFile("file")

        // Get raw file bytes - no reader method
        // openedFile, _ := formFile.Open()
        // file, _ := ioutil.ReadAll(openedFile)

        // Upload to disk
        // `formFile` has io.reader method
        // c.SaveUploadedFile(formFile, path)
        // --------------------
        
        c.String(http.StatusOK, "Files uploaded")
    })

    // Listen and serve on 0.0.0.0:8080
    router.Run(":8080")
}

Upload multiple files


Clients

HTML

<form action="/upload" method="POST" multiple="multiple">
  <input type="file" name="files">
  <input type="submit">
</form>

Curl

curl -X POST http://localhost:8080/upload \
  -F "files=../path-to-file/file1.zip" \
  -F "files=../path-to-file/file2.zip" \
  -H "Content-Type: multipart/form-data"

Server

Go

package main

import (
    "github.com/gin-gonic/gin"
    "log"
    "net/http"
    "io/ioutil"
)

type Form struct {
    Files []*multipart.FileHeader `form:"files" binding:"required"`
}

func main() {
    router := gin.Default()

    // Set a lower memory limit for multipart forms (default is 32 MiB)
    // router.MaxMultipartMemory = 8 << 20  // 8 MiB

    router.POST("/upload", func(c *gin.Context) {

        // Using `ShouldBind`
        // --------------------
        var form Form
        _ := c.ShouldBind(&form)

        // for _, formFile := range form.Files {

          // Get raw file bytes - no reader method
          // openedFile, _ := formFile.Open()
          // file, _ := ioutil.ReadAll(openedFile)

          // Upload to disk
          // `formFile` has io.reader method
          // c.SaveUploadedFile(formFile, path)

        // }
        // --------------------

        // Using `MultipartForm`
        // --------------------
        // form, _ := c.MultipartForm()
        // formFiles, _ := form["files[]"]

        // for _, formFile := range formFiles {

          // Get raw file bytes - no reader method
          // openedFile, _ := formFile.Open()
          // file, _ := ioutil.ReadAll(openedFile)

          // Upload to disk
          // `formFile` has io.reader method
          // c.SaveUploadedFile(formFile, path)

        // }
        // --------------------
        
        c.String(http.StatusOK, "Files uploaded")
    })

    // Listen and serve on 0.0.0.0:8080
    router.Run(":8080")
}

Upvotes: 9

reticentroot
reticentroot

Reputation: 3682

In your code you say var json Article where type article is defined as

type Article struct {
    Title   string `json:"title"`
    Body string `json:"body"`
    File    []byte `json:"file"`
}

And File is of type []byte. Type byte doesn't return anything other than what it holds

Your Article.File is not the same as r.FormFile where FormFile is a method that returns 3 items

So file, header, err := json.File isn't file, header, err := r.FormFile("foo")

See the implementation and method description from godocs -> here

Upvotes: 2

Related Questions