322

So I have the following, which seems incredibly hacky, and I've been thinking to myself that Go has better designed libraries than this, but I can't find an example of Go handling a POST request of JSON data. They are all form POSTs.

Here is an example request: curl -X POST -d "{\"test\": \"that\"}" http://localhost:8082/test

And here is the code, with the logs embedded:

package main

import (
    "encoding/json"
    "log"
    "net/http"
)

type test_struct struct {
    Test string
}

func test(rw http.ResponseWriter, req *http.Request) {
    req.ParseForm()
    log.Println(req.Form)
    //LOG: map[{"test": "that"}:[]]
    var t test_struct
    for key, _ := range req.Form {
        log.Println(key)
        //LOG: {"test": "that"}
        err := json.Unmarshal([]byte(key), &t)
        if err != nil {
            log.Println(err.Error())
        }
    }
    log.Println(t.Test)
    //LOG: that
}

func main() {
    http.HandleFunc("/test", test)
    log.Fatal(http.ListenAndServe(":8082", nil))
}

There's got to be a better way, right? I'm just stumped in finding what the best practice could be.

(Go is also known as Golang to the search engines, and mentioned here so others can find it.)

TomJ
  • 5,389
  • 4
  • 26
  • 31

9 Answers9

467

Please use json.Decoder instead of json.Unmarshal.

func test(rw http.ResponseWriter, req *http.Request) {
    decoder := json.NewDecoder(req.Body)
    var t test_struct
    err := decoder.Decode(&t)
    if err != nil {
        panic(err)
    }
    log.Println(t.Test)
}
Joe
  • 6,460
  • 1
  • 19
  • 15
  • 111
    Could you please explain why? – Ryan Bigg Jul 29 '13 at 02:41
  • 113
    For start, it looks like this can handle a stream rather than needing you to load it all into a buffer yourself. (I'm a different Joe BTW) – Joe Sep 05 '13 at 15:14
  • 12
    I wonder how proper error handling would look like in this case. I don't think it's a good idea to panic on an invalid json. –  Nov 25 '14 at 18:52
  • 2
    @skripted i believe the panic() is just an example. proper error handling would be however you would handle a request's critical/fatal errors, such as unable to decode json. normally one would have wrappers around their HTTPHandlers for middleware and related common security and such. Within that middleware/wrapper, I typically handle "error" responses from my HTTPHandlers as a generic Json error response to the end-user (so all responses, even errors, are json-encoded). That's just one of many ways to handle it. – eduncan911 Jan 19 '15 at 15:56
  • 1
    `panic` expects an argument, otherwise it doesn't compile – Deleplace Sep 24 '16 at 09:11
  • 1
    How do you handle an empty `r.Body` with this method? In my tests, no error is thrown. – thisisnotabus Feb 10 '17 at 07:55
  • 20
    I don't think you need to `defer req.Body.Close()` From the docs: "The Server will close the request body. The ServeHTTP Handler does not need to." Also to answer @thisisnotabus, from the docs: "For server requests the Request Body is always non-nil but will return EOF immediately when no body is present" https://golang.org/pkg/net/http/#Request – Drew LeSueur Mar 07 '17 at 22:25
  • 32
    I would suggest not using `json.Decoder`. It is intended for streams of JSON objects, not a single object. It is not more efficient for a single JSON object since it reads the entire object into memory. It has a downside that if garbage is included after the object it will not complain. Depending on a few factors, `json.Decoder` may not fully read the body and the connection will be ineligible for reuse. – Kale B May 21 '17 at 20:58
  • 1
    Kind of a side question, but is there a way to differentiate between if a user didn't pass a string versus if they passed empty string? I think they both default to empty string in the struct received. – user3125693 Jul 13 '17 at 19:49
  • why do we need to redeclare like " var t test_struct "? can't I just use " err := decoder.Decode(&test_struct) " ?? – Angger Aug 27 '17 at 16:47
  • 1
    @Angger - the decoder needs something to write to. `test_struct` is the type, `t` is the instance. It's not a redeclaration. It's declared first, then written to by the decoder. – Blake Caldwell Oct 25 '17 at 20:52
  • 2
    Downvoted, because you did not said why to not use json.Unmarshal. – Fjolnir Dvorak Aug 16 '18 at 20:48
  • 7
    No. Don't use `json.Decoder`. It silently passes several bad inputs (e.g. `{} {"foo": "bar"}` and is meant for streams of json objects. This answer should be unaccepted - it is harmful and has already caused issues with my testing. – Cesar Feb 08 '19 at 20:12
  • 1
    @Cesar as of `go 1.10 json.Decoders` gained the method `DisallowUnknownFields()` - see my answer on how this method can be used to detect bad user input along with other decoder methods. – colm.anseo Mar 07 '19 at 21:13
  • @FjolnirDvorak I address your comment in my answer below – colm.anseo Mar 07 '19 at 21:14
  • i've voted this down.. till such a time as author provides clearer answer – Somo S. Jan 04 '20 at 21:13
  • @KaleB Even json.Unmarshal has the downside that if garbage is included after the object it won't complain. From the docs: By default, object keys which don't have a corresponding struct field are ignored (see Decoder.DisallowUnknownFields for an alternative). – Cirelli94 Sep 14 '20 at 10:51
  • 1
    @Cirelli94 Unknown keys aren't the same as additional data after the JSON object. https://play.golang.org/p/ZEmzwXCloGi – Kale B Sep 25 '20 at 19:44
110

You need to read from req.Body. The ParseForm method is reading from the req.Body and then parsing it in standard HTTP encoded format. What you want is to read the body and parse it in JSON format.

Here's your code updated.

package main

import (
    "encoding/json"
    "log"
    "net/http"
    "io/ioutil"
)

type test_struct struct {
    Test string
}

func test(rw http.ResponseWriter, req *http.Request) {
    body, err := ioutil.ReadAll(req.Body)
    if err != nil {
        panic(err)
    }
    log.Println(string(body))
    var t test_struct
    err = json.Unmarshal(body, &t)
    if err != nil {
        panic(err)
    }
    log.Println(t.Test)
}

func main() {
    http.HandleFunc("/test", test)
    log.Fatal(http.ListenAndServe(":8082", nil))
}
MDS
  • 497
  • 1
  • 5
  • 16
Daniel
  • 38,041
  • 11
  • 92
  • 73
  • Thanks! I see where I was going wrong now. If you call ```req.ParseForm()```, which I was doing in earlier attempts of trying to solve this problem, before you try and read the ```req.Body```, it seems to clear the body out and ```unexpected end of JSON input``` is thrown when you go to ```Unmarshal``` (at least in 1.0.2) – TomJ Mar 28 '13 at 01:46
  • 1
    @Daniel: When I do curl -X POST -d "{\"tes\": \"that\"}" http://localhost:8082/test, log.Println(t.Test) returns empty. Why ? Or for that matter if post any other JSON, it returns empty – Somesh Oct 30 '14 at 12:46
  • Your POST request is wrong. tes != test. Appreciate that was 5 years ago :/ – Rambatino Feb 08 '19 at 04:19
  • This is a nice simple example! – 15412s Jan 11 '20 at 11:24
  • This is good advice, but to be clear, the answers referring to the use of `json.NewDecoder(req.Body)` are also correct. – Rich Mar 21 '20 at 22:01
99

There are two reasons why json.Decoder should be preferred over json.Unmarshal - that are not addressed in the most popular answer from 2013:

  1. February 2018, go 1.10 introduced a new method json.Decoder.DisallowUnknownFields() which addresses the concern of detecting unwanted JSON-input
  2. req.Body is already an io.Reader. Reading its entire contents and then performing json.Unmarshal wastes resources if the stream was, say a 10MB block of invalid JSON. Parsing the request body, with json.Decoder, as it streams in would trigger an early parse error if invalid JSON was encountered. Processing I/O streams in realtime is the preferred go-way.

Addressing some of the user comments about detecting bad user input:

To enforce mandatory fields, and other sanitation checks, try:

d := json.NewDecoder(req.Body)
d.DisallowUnknownFields() // catch unwanted fields

// anonymous struct type: handy for one-time use
t := struct {
    Test *string `json:"test"` // pointer so we can test for field absence
}{}

err := d.Decode(&t)
if err != nil {
    // bad JSON or unrecognized json field
    http.Error(rw, err.Error(), http.StatusBadRequest)
    return
}

if t.Test == nil {
    http.Error(rw, "missing field 'test' from JSON object", http.StatusBadRequest)
    return
}

// optional extra check
if d.More() {
    http.Error(rw, "extraneous data after JSON object", http.StatusBadRequest)
    return
}

// got the input we expected: no more, no less
log.Println(*t.Test)

Playground

Typical output:

$ curl -X POST -d "{}" http://localhost:8082/strict_test

expected json field 'test'

$ curl -X POST -d "{\"Test\":\"maybe?\",\"Unwanted\":\"1\"}" http://localhost:8082/strict_test

json: unknown field "Unwanted"

$ curl -X POST -d "{\"Test\":\"oops\"}g4rB4g3@#$%^&*" http://localhost:8082/strict_test

extraneous data after JSON

$ curl -X POST -d "{\"Test\":\"Works\"}" http://localhost:8082/strict_test 

log: 2019/03/07 16:03:13 Works
colm.anseo
  • 19,337
  • 4
  • 43
  • 52
  • 19
    Thank you for explaining opinions instead of just stating that something is bad – Fjolnir Dvorak Aug 21 '19 at 12:58
  • do you know what it doesn't handle? i saw Test can be in json twice and it accepts 2nd occurrence – tooptoop4 Dec 24 '19 at 10:48
  • @tooptoop4 one would need to write a custom decoder to warn about duplicate fields - adding inefficiencies to the decoder - all to handle a scenario that would never happen. No standard JSON encoder would ever produce duplicate fields. – colm.anseo Jan 28 '20 at 13:58
  • `More()` does not really do what you seem to expect it to do: your example will happily accept `{}]` as valid JSON – Roel Harbers Mar 19 '22 at 22:04
82

I was driving myself crazy with this exact problem. My JSON Marshaller and Unmarshaller were not populating my Go struct. Then I found the solution at https://eager.io/blog/go-and-json:

"As with all structs in Go, it’s important to remember that only fields with a capital first letter are visible to external programs like the JSON Marshaller."

After that, my Marshaller and Unmarshaller worked perfectly!

colm.anseo
  • 19,337
  • 4
  • 43
  • 52
Steve Stilson
  • 1,015
  • 7
  • 11
22

I found the following example from the docs really helpful (source here).

package main

import (
    "encoding/json"
    "fmt"
    "io"
    "log"
    "strings"
)

func main() {
    const jsonStream = `
        {"Name": "Ed", "Text": "Knock knock."}
        {"Name": "Sam", "Text": "Who's there?"}
        {"Name": "Ed", "Text": "Go fmt."}
        {"Name": "Sam", "Text": "Go fmt who?"}
        {"Name": "Ed", "Text": "Go fmt yourself!"}
    `
    type Message struct {
        Name, Text string
    }
    dec := json.NewDecoder(strings.NewReader(jsonStream))
    for {
        var m Message
        if err := dec.Decode(&m); err == io.EOF {
            break
        } else if err != nil {
            log.Fatal(err)
        }
        fmt.Printf("%s: %s\n", m.Name, m.Text)
    }
}

The key here being that the OP was looking to decode

type test_struct struct {
    Test string
}

...in which case we would drop the const jsonStream, and replace the Message struct with the test_struct:

func test(rw http.ResponseWriter, req *http.Request) {
    dec := json.NewDecoder(req.Body)
    for {
        var t test_struct
        if err := dec.Decode(&t); err == io.EOF {
            break
        } else if err != nil {
            log.Fatal(err)
        }
        log.Printf("%s\n", t.Test)
    }
}

Update: I would also add that this post provides some great data about responding with JSON as well. The author explains struct tags, which I was not aware of.

Since JSON does not normally look like {"Test": "test", "SomeKey": "SomeVal"}, but rather {"test": "test", "somekey": "some value"}, you can restructure your struct like this:

type test_struct struct {
    Test string `json:"test"`
    SomeKey string `json:"some-key"`
}

...and now your handler will parse JSON using "some-key" as opposed to "SomeKey" (which you will be using internally).

User 1058612
  • 3,739
  • 1
  • 27
  • 33
7

I like to define custom structs locally. So:

// my handler func
func addImage(w http.ResponseWriter, r *http.Request) {

    // define custom type
    type Input struct {
        Url        string  `json:"url"`
        Name       string  `json:"name"`
        Priority   int8    `json:"priority"`
    }

    // define a var 
    var input Input

    // decode input or return error
    err := json.NewDecoder(r.Body).Decode(&input)
    if err != nil {
        w.WriteHeader(400)
        fmt.Fprintf(w, "Decode error! please check your JSON formating.")
        return
    }

    // print user inputs
    fmt.Fprintf(w, "Inputed name: %s", input.Name)

}
Amin Shojaei
  • 5,451
  • 2
  • 38
  • 46
4
type test struct {
    Test string `json:"test"`
}

func test(w http.ResponseWriter, req *http.Request) {
    var t test_struct

    body, _ := ioutil.ReadAll(req.Body)
    json.Unmarshal(body, &t)

    fmt.Println(t)
}
charlesreid1
  • 4,360
  • 4
  • 30
  • 52
1

Earlier ReadAll func was part of ioutil package, later it got deprecated. But now the io package itself has the ReadAll func.

type test struct {
  Test string `json:"test"`
}

func test(w http.ResponseWriter, req *http.Request) {
  var t test_struct
  body, _ := io.ReadAll(req.Body)
  json.Unmarshal(body, &t)
  fmt.Println(t)
}
R.Sutliya
  • 76
  • 3
0

Shorter code:

type PostMessage struct {
    Action string
}

func Execute(w http.ResponseWriter, r *http.Request) {

    var t PostMessage

    err := json.NewDecoder(r.Body).Decode(&t)
    if err != nil {
        fmt.Println(err)
    }

    fmt.Println(t.Action)
}
Boris Yakubchik
  • 3,861
  • 3
  • 34
  • 41