123
votes

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, "", "  ")
}
5

5 Answers

155
votes

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)
44
votes

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)
}
43
votes

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)
2
votes

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.

1
votes

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.