Como servir uma resposta JSON usando Go?

104

Pergunta: Atualmente estou imprimindo minha resposta func Index desta fmt.Fprintf(w, string(response)) forma, porém, como posso enviar JSON corretamente na solicitação para que seja consumido por uma visualização?

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, "", "  ")
}
Matthew Harwood
fonte
github.com/unrolled/render também pode ajudar.
elithrar

Respostas:

132

Você pode definir seu cabeçalho de tipo de conteúdo para que os clientes saibam o que esperar do json

w.Header().Set("Content-Type", "application/json")

Outra maneira de empacotar uma estrutura para json é construir um codificador usando o http.ResponseWriter

// get a payload p := Payload{d}
json.NewEncoder(w).Encode(p)
dm03514
fonte
12
Embora w.Header().Set("Content-Type", "application/json")seja correto para definir o tipo de conteúdo, ele não faz quando uso em json.NewEncodervez disso, eu obtenho um resultado txt / simples. Tem mais alguém pegando isso. A resposta de @poorva funcionou conforme o esperado
Jaybeecave
2
Risca isso. Se eu usar w.WriteHeader(http.StatusOk) , obtenho o resultado acima.
Jaybeecave
4
Se eu usar, w.WriteHeader(http.StatusOk)então recebo text/plain; charset=utf-8, se não definir o código de status explicitamente recebo applicaton/jsone a resposta ainda tem um código de status 200.
Ramon Rambo
2
Hmmm ... poderia ter a ver com os documentos aqui ? Changing the header map after a call to WriteHeader (or Write) has no effect unless the modified headers are trailers.
Dan Esparza
2
Adicionando trabalho w.Header().Set("Content-Type", "application/json")acima json.NewEncoder(w).Encode(p)para mim
Ardi Nusawan
37

Outros usuários comentando que Content-Typeé plain/textdurante a codificação. Você deve definir o Content-Typeprimeiro e w.Header().Set, em seguida, o código de resposta HTTP w.WriteHeader.

Se você ligar w.WriteHeaderprimeiro, ligue w.Header().Setdepois de receber plain/text.

Um exemplo de manipulador pode ser parecido com este;

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)
}
Daniel R.
fonte
Como retornar uma resposta, se meu programa entrar em pânico? Tentei usar o recover () e depois retornei do seu, mas não deu certo.
infiniteLearner
31

Você pode fazer algo assim em sua getJsonResponsefunção -

jData, err := json.Marshal(Data)
if err != nil {
    // handle error
}
w.Header().Set("Content-Type", "application/json")
w.Write(jData)
pobreva
fonte
2
Uma observação importante sobre esta versão é que ela usa uma fatia de byte jData, possivelmente desnecessariamente. Datapode ser de tamanho arbitrário, dependendo dos dados que estão sendo organizados, portanto, isso pode ser um desperdício de memória não trivial. Após o marshalling, copiamos da memória para o ResponseWriterfluxo. A resposta que usa json.NewEncoder () etc. escreveria o JSON empacotado diretamente no ResponseWriter(em seu fluxo ..)
Jonno
1
Funcionou para mim! O problema ocorreu quando 'w.WriteHeader (http.StatusCreated)' foi adicionado antes ou depois.
darkdefender27
1
Não há necessidade de voltar após o pânico, pois isso sai de seu programa
andersfylling
Pelo menos esta solução não adiciona o final \ n da Encoder.Encode()função
Jonathan Muller
@Jonno você está certo, mas é a única resposta quando você pode verificar se a codificação está indo bem ANTES de escrever o cabeçalho, porque uma vez escrito ele pode ser alterado!
Cirelli94
2

No framework gobuffalo.io, fiz funcionar assim:

// 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"))
}

e então, quando eu quiser obter uma resposta JSON para esse recurso, tenho que definir "Content-type" como "application / json" e funciona.

Acho que Rails tem uma maneira mais conveniente de lidar com vários tipos de resposta, não vi o mesmo no gobuffalo até agora.

Aleks Tkachenko
fonte
0

Você pode usar este renderizador de pacote , eu escrevi para resolver este tipo de problema, é um wrapper para servir JSON, JSONP, XML, HTML etc.


fonte