This is a pretty common task: encode JSON and send it to a server, decode JSON on the server, and vice versa. Amazingly, the existing resources on how to do this aren't very clear. So let's walk through each case, for the following simple User object:
type User struct{ Id string Balance uint64 }
Sending JSON in the body of a POST/PUT request
Let's start with the trickiest one: the body of a Go's http.Request
is an
io.Reader
, which doesn't fit well if you have a struct
- you need to write
the struct first and then copy that to a reader.
func main() { u := User{Id: "US123", Balance: 8} b := new(bytes.Buffer) json.NewEncoder(b).Encode(u) res, _ := http.Post("https://httpbin.org/post", "application/json; charset=utf-8", b) io.Copy(os.Stdout, res.Body) }
Decoding JSON on the server
Let's say you're expecting the client to send JSON data to the server. Easy,
decode it with json.NewDecoder(r.Body).Decode(&u)
. Here's what that looks
like with error handling:
func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { var u User if r.Body == nil { http.Error(w, "Please send a request body", 400) return } err := json.NewDecoder(r.Body).Decode(&u) if err != nil { http.Error(w, err.Error(), 400) return } fmt.Println(u.Id) }) log.Fatal(http.ListenAndServe(":8080", nil)) }
Encoding JSON in a server response
Just the opposite of the above - call json.NewEncoder(w).Encode(&u)
to write
JSON to the server.
func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { u := User{Id: "US123", Balance: 8} json.NewEncoder(w).Encode(u) }) log.Fatal(http.ListenAndServe(":8080", nil)) }
Reading a JSON response from the server.
This time you're going to read the response body in the client, after making the request.
func main() { u := User{Id: "US123", Balance: 8} b := new(bytes.Buffer) json.NewEncoder(b).Encode(u) res, _ := http.Post("https://httpbin.org/post", "application/json; charset=utf-8", b) var body struct { // httpbin.org sends back key/value pairs, no map[string][]string Headers map[string]string `json:"headers"` Origin string `json:"origin"` } json.NewDecoder(res.Body).Decode(&body) fmt.Println(body) }
That's it! I hope it helps you a lot. Note that we only had to encode/decode the response to a byte array one time - in every other case we passed it directly to the reader/writer, which is one of the really nice things about Go, and interfaces - in most cases it's really easy to pass around streams, instead of having to deal with the intermediate steps.
Liked what you read? I am available for hire.
How have you found the code `if r.Body == nil` behaves in live? I have the exact same code and I found when I was doing `curl localhost:8080/products` this condition was never hitting. I was masking this in my test because I was using `req, err := http.NewRequest(“GET”, “/products”, nil)`. What have your experiences been?
That might be the case. I believe the Go team fixed it to always populate the body in a recent release.