How to serve up a JSON response using Go? - json

Question: Currently I'm printing out my response in the func Index
like this fmt.Fprintf(w, string(response)) however, how can I send JSON properly in the request so that it maybe consumed by a view?
package main
import (
"fmt"
"github.com/julienschmidt/httprouter"
"net/http"
"log"
"encoding/json"
)
type Payload struct {
Stuff Data
}
type Data struct {
Fruit Fruits
Veggies Vegetables
}
type Fruits map[string]int
type Vegetables map[string]int
func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
response, err := getJsonResponse();
if err != nil {
panic(err)
}
fmt.Fprintf(w, string(response))
}
func main() {
router := httprouter.New()
router.GET("/", Index)
log.Fatal(http.ListenAndServe(":8080", router))
}
func getJsonResponse()([]byte, error) {
fruits := make(map[string]int)
fruits["Apples"] = 25
fruits["Oranges"] = 10
vegetables := make(map[string]int)
vegetables["Carrats"] = 10
vegetables["Beets"] = 0
d := Data{fruits, vegetables}
p := Payload{d}
return json.MarshalIndent(p, "", " ")
}

You can set your content-type header so clients know to expect json
w.Header().Set("Content-Type", "application/json")
Another way to marshal a struct to json is to build an encoder using the http.ResponseWriter
// get a payload p := Payload{d}
json.NewEncoder(w).Encode(p)

Other users were commenting that the Content-Type is plain/text when encoding.
You have to set the content type with w.Header().Set() first, then write the HTTP response code with w.WriteHeader().
If you call w.WriteHeader() first, then call w.Header().Set() after you will get plain/text.
An example handler might look like this:
func SomeHandler(w http.ResponseWriter, r *http.Request) {
data := SomeStruct{}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(data)
}

You can do something like this in you getJsonResponse function -
jData, err := json.Marshal(Data)
if err != nil {
// handle error
}
w.Header().Set("Content-Type", "application/json")
w.Write(jData)

In gobuffalo.io framework I got it to work like this:
// say we are in some resource Show action
// some code is omitted
user := &models.User{}
if c.Request().Header.Get("Content-type") == "application/json" {
return c.Render(200, r.JSON(user))
} else {
// Make user available inside the html template
c.Set("user", user)
return c.Render(200, r.HTML("users/show.html"))
}
and then when I want to get JSON response for that resource I have to set "Content-type" to "application/json" and it works.
I think Rails has more convenient way to handle multiple response types, I didn't see the same in gobuffalo so far.

You may use this package renderer, I have written to solve this kind of problem, it's a wrapper to serve JSON, JSONP, XML, HTML etc.

This is a complement answer with a proper example:
func (ch captureHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodPost:
body, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, fmt.Sprintf("error reading request body, %v", err), http.StatusInternalServerError)
return
}
...do your stuff here...
case http.MethodGet:
w.Header().Set("Content-Type", "application/json")
err := json.NewEncoder(w).Encode( ...put your object here...)
if err != nil {
http.Error(w, fmt.Sprintf("error building the response, %v", err), http.StatusInternalServerError)
return
}
default:
http.Error(w, fmt.Sprintf("method %s is not allowed", r.Method), http.StatusMethodNotAllowed)
}
}

Related

How to minimize duplicate code in Go Mux when always trying to return same response structure?

I have tons of code similar to the following code snippet that I just try to fill my response struct, json marshal the output, set status code and return the result:
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
response := responses.UserResponse{
Status: http.StatusBadRequest,
Message: "error",
Data: map[string]interface{}{"error": err.Error()},
}
rw.WriteHeader(http.StatusBadRequest)
errRes, _ := json.Marshal(response)
rw.Write(errRes)
return
}
I tried to create a function that receives r variable (request.http) to receive the body and also status code of the response. But noticed that I have to again check error code outside of the function and then do the same response creation flow again.
How someone expert in Go tries to minimize code duplications like these? Is this OK to have code duplication like these in first place?
Minimize code duplication by moving the decode call and error handling to a reusable function:
// Decode returns true if the request body is successfully decoded
// to the value pointed to by pv. Otherwise, decode writes an error
// response and returns false.
func decode(rw http.ResponseWriter, r *http.Request, pv interface{}) bool {
err := json.NewDecoder(r.Body).Decode(pv)
if err == nil {
return true
}
rw.WriteHeader(http.StatusBadRequest)
json.NewEncoder(rw).Encode(map[string]any{
"status": http.StatusBadRequest,
"message": "error",
"data": map[string]any{"error": err.Error()},
})
return false
}
Use the function like this:
func userHandler(rw http.ResponseWriter, r *http.Request) {
var u UserRequest
if !decode(rw, r, &u) {
return
}
}
It is preferable to abstract details to provide a high-level picture of what your handler does.
func (h *rideHandler) handleCancelRideByPassenger(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
user := getUser(ctx)
req := &cancelRequest{}
if err := decode(r, req); err != nil {
h.logger.Error("cancel ride: problem while decoding body request", zap.String("ip", r.RemoteAddr), zap.Error(err))
h.respond.BadRequest(w, NewRESTError(reasonDecoding, "problem while decoding input parameters"))
return
}
req.PublicID = chi.URLParam(r, "id")
err := h.rideService.CancelRide(ctx, req, user)
if err != nil {
var validationErr *ValidationError
switch {
case errors.As(err, &validationErr):
h.respond.BadRequest(w, NewRESTValidationError(reasonValidation, "problem while validating request", validationErr))
return
default:
h.respond.InternalServerError(w, NewRESTError(reasonInternalError, "unknown problem occurred"))
return
}
}
h.respond.Ok(w, NewRESTResponse(&cancelRideResponse{Success: true}))
}
Handler utilizes some handy sugar functions to remove duplication and provide high-level overview of what handler does instead underlying details.
func decode(request *http.Request, val interface{}) error {
dec := json.NewDecoder(request.Body)
dec.DisallowUnknownFields()
return dec.Decode(val)
}
type Responder struct {
Encoder Encoder
Before BeforeFunc
After AfterFunc
OnError OnErrorFunc
}
func (r *Responder) writeResponse(w http.ResponseWriter, v interface{}, status int) {
if r.Before != nil {
status, v = r.Before(w, v, status)
}
encoder := JSON
if r.Encoder != nil {
encoder = r.Encoder
}
w.Header().Set("Content-Type", encoder.ContentType())
w.WriteHeader(status)
if err := encoder.Encode(w, v); err != nil {
if r.OnError != nil {
r.OnError(err)
}
}
if r.After != nil {
r.After(v, status)
}
}
func (r *Responder) Ok(w http.ResponseWriter, v interface{}) {
r.writeResponse(w, v, http.StatusOK)
}
Probably you should write your own respond package or check what is available in open source. Then you can use this respond package with the same response structure everywhere.

How do you send a struct in a GET request

If I create a struct, how do I send it with a HTTP GET request to a web server endpoint?
package main
import (
"encoding/json"
"net/http"
)
type Payload struct {
Endpoint string `json:"endpoint"`
Data map[string]interface{} `json:"data"`
}
/*
eg.
{"endpoint":"some-service", "data": {"userID": "abc123"}}
*/
func main() {
http.HandleFunc("/service", func(w http.ResponseWriter, r *http.Request) {
decoder := json.NewDecoder(r.Body)
var p Payload
err := decoder.Decode(&p)
if err != nil {
panic(err)
}
// How to attach 'p' ?
resp, err := http.Get("www.example.com/" + p.Endpoint) // Add "data": p.Data
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
// handle response here
})
http.ListenAndServe(":8080", nil)
}
The endpoint receiving this data would ideally interpret it as JSON.
HTTP GET requests do not allow a request body.
If you must do it with a GET, basically you have 2 options: add the data as a query parameter, or send it in an HTTP Header field.
Note that both the URL and header fields have length limits, so if you want to "attach" a long JSON text, it might fail. To send arbitrary data, you should use another method, e.g. POST.
Example adding it as a query param:
u, err := url.Parse("http://www.example.com")
if err != nil {
panic(err)
}
params := url.Values{}
params.Add("data", `{"a":1,"b":"c"}`)
u.RawQuery = params.Encode()
// use u.String() as the request URL
Example sending it in a Header field:
req, err := http.NewRequest("GET", "http://example.com", nil)
// ...
req.Header.Add("X-Data", `{"a":1,"b":"c"}`)
resp, err := client.Do(req)

Printing decoded JSON in Golang

I am very new to Go / programming in general - having just picked it up whilst messing about creating my own crypto currency portfolio web site.
I am struggling printing to the web server output. If I used Printf - it prints to console but as soon as I use Fprintf to print to the web app, I get a number of errors which I can't seem to solve.
Could someone walk me through it?
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
)
type Obsidian []struct {
PriceUsd string `json:"price_usd"`
PriceBtc string `json:"price_btc"`
}
func webserver(w http.ResponseWriter, r *http.Request) {
url := "https://api.coinmarketcap.com/v1/ticker/obsidian/"
req, err := http.NewRequest("GET", url, nil)
if err != nil {
log.Fatal("NewRequest: ", err)
return
}
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
log.Fatal("Do: ", err)
return
}
defer resp.Body.Close()
var record Obsidian
if err := json.NewDecoder(resp.Body).Decode(&record); err != nil {
log.Println(err)
}
fmt.Printf("%+v", record)
}
func main() {
http.HandleFunc("/test", webserver)
http.ListenAndServe(":8001", nil)
}
I have tried to replace:
fmt.Printf("%+v", record)
with:
fmt.Fprintf("%+v", record)
and receive the following errors:
./test.go:54:21: cannot use "%+v" (type string) as type io.Writer in argument to fmt.Fprintf:
string does not implement io.Writer (missing Write method)
./test.go:54:21: cannot use record (type Obsidian) as type string in argument to fmt.Fprintf
Thanks to #MiloChrisstiansen
fmt.Fprintf(w, "%+v", record)
You could also use
w.Write([]byte(record))

How do I send a JSON string in a POST request in Go

I tried working with Apiary and made a universal template to send JSON to mock server and have this code:
package main
import (
"encoding/json"
"fmt"
"github.com/jmcvetta/napping"
"log"
"net/http"
)
func main() {
url := "http://restapi3.apiary.io/notes"
fmt.Println("URL:>", url)
s := napping.Session{}
h := &http.Header{}
h.Set("X-Custom-Header", "myvalue")
s.Header = h
var jsonStr = []byte(`
{
"title": "Buy cheese and bread for breakfast."
}`)
var data map[string]json.RawMessage
err := json.Unmarshal(jsonStr, &data)
if err != nil {
fmt.Println(err)
}
resp, err := s.Post(url, &data, nil, nil)
if err != nil {
log.Fatal(err)
}
fmt.Println("response Status:", resp.Status())
fmt.Println("response Headers:", resp.HttpResponse().Header)
fmt.Println("response Body:", resp.RawText())
}
This code doesn't send JSON properly, but I don't know why. The JSON string can be different in every call. I can't use Struct for this.
I'm not familiar with napping, but using Golang's net/http package works fine (playground):
func main() {
url := "http://restapi3.apiary.io/notes"
fmt.Println("URL:>", url)
var jsonStr = []byte(`{"title":"Buy cheese and bread for breakfast."}`)
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))
req.Header.Set("X-Custom-Header", "myvalue")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
fmt.Println("response Status:", resp.Status)
fmt.Println("response Headers:", resp.Header)
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println("response Body:", string(body))
}
you can just use post to post your json.
values := map[string]string{"username": username, "password": password}
jsonValue, _ := json.Marshal(values)
resp, err := http.Post(authAuthenticatorUrl, "application/json", bytes.NewBuffer(jsonValue))
If you already have a struct.
import (
"bytes"
"encoding/json"
"io"
"net/http"
"os"
)
// .....
type Student struct {
Name string `json:"name"`
Address string `json:"address"`
}
// .....
body := &Student{
Name: "abc",
Address: "xyz",
}
payloadBuf := new(bytes.Buffer)
json.NewEncoder(payloadBuf).Encode(body)
req, _ := http.NewRequest("POST", url, payloadBuf)
client := &http.Client{}
res, e := client.Do(req)
if e != nil {
return e
}
defer res.Body.Close()
fmt.Println("response Status:", res.Status)
// Print the body to the stdout
io.Copy(os.Stdout, res.Body)
Full gist.
In addition to standard net/http package, you can consider using my GoRequest which wraps around net/http and make your life easier without thinking too much about json or struct. But you can also mix and match both of them in one request! (you can see more details about it in gorequest github page)
So, in the end your code will become like follow:
func main() {
url := "http://restapi3.apiary.io/notes"
fmt.Println("URL:>", url)
request := gorequest.New()
titleList := []string{"title1", "title2", "title3"}
for _, title := range titleList {
resp, body, errs := request.Post(url).
Set("X-Custom-Header", "myvalue").
Send(`{"title":"` + title + `"}`).
End()
if errs != nil {
fmt.Println(errs)
os.Exit(1)
}
fmt.Println("response Status:", resp.Status)
fmt.Println("response Headers:", resp.Header)
fmt.Println("response Body:", body)
}
}
This depends on how you want to achieve. I made this library because I have the same problem with you and I want code that is shorter, easy to use with json, and more maintainable in my codebase and production system.
Example post request for http or https
//Encode the data
postBody, _ := json.Marshal(map[string]string{
"name": "Test",
"email": "Test#Test.com",
})
responseBody := bytes.NewBuffer(postBody)
//Leverage Go's HTTP Post function to make request
resp, err := http.Post("https://postman-echo.com/post", "application/json", responseBody)
//Handle Error
if err != nil {
log.Fatalf("An Error Occured %v", err)
}
defer resp.Body.Close()
//Read the response body
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatalln(err)
}
sb := string(body)
log.Printf(sb)
Use io.Pipe for large request bodies as mentioned in another answer. This approach avoids building the entire request body in memory by streaming the data from the JSON encoder to the network.
This answer builds on the other answer by showing how to handle errors. Always handle errors!
Use the pipe's CloseWithError function to propagate encoding errors back to error returned from http.Post.
Handle the error returned from http.Post
Close the response body.
Here's the code:
r, w := io.Pipe()
go func() {
w.CloseWithError(json.NewEncoder(w).Encode(data))
}()
// Ensure that read side of pipe is closed. This
// unblocks goroutine in scenario where http.Post
// errors out before reading the entire request body.
defer r.Close()
resp, err := http.Post(url, r)
if err != nil {
// Adjust error handling here to meet application requrirements.
log.Fatal(err)
}
defer resp.Body.Close()
// Use the response here.
If you have a lot of data to send, you can use a pipe:
package main
import (
"encoding/json"
"io"
"net/http"
)
func main() {
m := map[string]int{"SNG_ID": 75498415}
r, w := io.Pipe()
go func() {
json.NewEncoder(w).Encode(m)
w.Close()
}()
http.Post("https://stackoverflow.com", "application/json", r)
}
https://golang.org/pkg/io#Pipe
if you want to do it like that, you need to use this map for unmarshalling json string.
var data map[string]interface{}
but if you need to change the json each time and to make initialization of your requst body more convenient, you can use this map for creating json body.
var bodyJsonMap map[string]interface{}{
"key1": val1,
"key2": val2,
...
}
Then marshal it to a json-string.

How to get JSON object by calling a url in Go Language?

I'm starting to learn Golang and I would like to know how to get a json response by calling an url, if you could give me an example it would be great in order to guide myself.
Here's a simple example to get you started. Instead of a map[string]interface{} you should consider making a struct to hold the result of your request.
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
)
func main() {
resp, err := http.Get("http://api.geonames.org/citiesJSON?north=44.1&south=-9.9&east=-22.4&west=55.2&lang=de&username=demo")
if err != nil {
log.Fatal(err)
}
var generic map[string]interface{}
err = json.NewDecoder(resp.Body).Decode(&generic)
if err != nil {
log.Fatal(err)
}
fmt.Println(generic)
}
I'd write a little helper function to do it:
// getJSON fetches the contents of the given URL
// and decodes it as JSON into the given result,
// which should be a pointer to the expected data.
func getJSON(url string, result interface{}) error {
resp, err := http.Get(url)
if err != nil {
return fmt.Errorf("cannot fetch URL %q: %v", url, err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("unexpected http GET status: %s", resp.Status)
}
// We could check the resulting content type
// here if desired.
err := json.NewDecoder(resp.Body).Decode(result)
if err != nil {
return fmt.Errorf("cannot decode JSON: %v", err)
}
return nil
}
A full working example can be found here: http://play.golang.org/p/b1WJb7MbQV
Note that it is important to check the status code as well as the Get error, and the response body must be closed explicitly (see the documentation here: http://golang.org/pkg/net/http/#Get)