Tratamento da solicitação de postagem JSON no Go


250

Então, eu tenho o seguinte, que parece incrivelmente hacky, e eu tenho pensado comigo mesmo que o Go tem bibliotecas projetadas melhor do que isso, mas não consigo encontrar um exemplo de Go manipulando uma solicitação POST de dados JSON. Todos são POSTs de formulário.

Aqui está um exemplo de solicitação: curl -X POST -d "{\"test\": \"that\"}" http://localhost:8082/test

E aqui está o código, com os logs incorporados:

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

Tem que haver uma maneira melhor, certo? Estou perplexo ao descobrir qual poderia ser a melhor prática.

(Go também é conhecido como Golang para os mecanismos de pesquisa e mencionado aqui para que outros possam encontrá-lo.)


3
se você usa curl -X POST -H 'Content-Type: application/json' -d "{\"test\": \"that\"}", então req.Form["test"]deve retornar"that"
Vinicius

@ Vinicius existem provas disso?
diralik

Respostas:


388

Por favor, use em json.Decodervez de 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)
}

79
Poderia explicar por que?
Ryan Bigg

86
Para começar, parece que isso pode lidar com um fluxo em vez de precisar carregar tudo em um buffer. (Eu sou um Joe diferente BTW)
Joe

7
Gostaria de saber como seria o tratamento adequado de erros nesse caso. Não acho que seja uma boa ideia entrar em pânico com um json inválido.
codepushr

15
Não acho que você precise defer req.Body.Close()Dos documentos: "O servidor fechará o corpo da solicitação. O manipulador ServeHTTP não precisa". Também para responder @thisisnotabus, nos documentos: "Para solicitações do servidor, o Corpo da Solicitação é sempre nulo, mas retornará o EOF imediatamente quando nenhum corpo estiver presente" golang.org/pkg/net/http/#Request
Drew LeSueur em

22
Eu sugeriria não usar json.Decoder. Ele é destinado a fluxos de objetos JSON, não a um único objeto. Não é mais eficiente para um único objeto JSON, pois ele lê o objeto inteiro na memória. Tem uma desvantagem de que, se o lixo for incluído após o objeto, ele não reclamará. Dependendo de alguns fatores, json.Decoderpode não ser totalmente lido o corpo e a conexão não estará qualificada para reutilização.
Kale B

85

Você precisa ler req.Body. O ParseFormmétodo está lendo req.Bodye analisando-o no formato codificado em HTTP padrão. O que você deseja é ler o corpo e analisá-lo no formato JSON.

Aqui está seu código atualizado.

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

Obrigado! Eu vejo onde eu estava errado agora. Se você telefonar req.ParseForm(), o que eu estava fazendo nas tentativas anteriores de tentar resolver esse problema, antes de tentar ler o req.Bodyunexpected end of JSON inputUnmarshal
arquivo

1
@ Daniel: Quando eu enrolar -X POST -d "{\" tes \ ": \" that \ "}" host local: 8082 / test , log.Println (t.Test) retorna vazio. Por quê ? Ou para que o assunto se postar qualquer outro JSON, ele retorna vazio
Somesh

Sua solicitação POST está errada. tes! = teste. Aprecie isso há 5 anos: /
Rambatino 08/02/19

Este é um bom exemplo simples!
15412s

Esse é um bom conselho, mas, para ficar claro, as respostas referentes ao uso de json.NewDecoder(req.Body)também estão corretas.
Rich

59

Eu estava enlouquecendo com esse problema exato. Meu JSON Marshaller e Unmarshaller não estavam preenchendo minha estrutura Go. Encontrei a solução em https://eager.io/blog/go-and-json :

"Como em todas as estruturas do Go, é importante lembrar que apenas os campos com uma primeira letra maiúscula são visíveis para programas externos como o JSON Marshaller".

Depois disso, meu Marshaller e Unmarshaller funcionaram perfeitamente!


Inclua alguns trechos do link. Se for preterido, os exemplos serão perdidos.
030

47

Há duas razões pelas quais json.Decoderdeve ser preferível ao invés json.Unmarshal- que não são abordadas na resposta mais popular de 2013:

  1. Em fevereiro de 2018, go 1.10introduziu um novo método json.Decoder.DisallowUnknownFields (), que aborda a preocupação de detectar entradas JSON indesejadas
  2. req.Bodyjá é um io.Reader. Lendo todo o seu conteúdo e, em seguida, realizando json.Unmarshaldesperdícios de recursos, se o fluxo for, digamos, um bloco de 10 MB de JSON inválido. A análise do corpo da solicitação, com json.Decoder, conforme ele é transmitido , acionaria um erro de análise antecipada se JSON inválido fosse encontrado. O processamento de fluxos de E / S em tempo real é o caminho preferido .

Abordando alguns dos comentários do usuário sobre a detecção de entrada incorreta do usuário:

Para impor campos obrigatórios e outras verificações de saneamento, tente:

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)

Parque infantil

Saída típica:

$ 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

6
Obrigado por explicar opiniões em vez de apenas afirmando que algo é ruim
Fjolnir Dvorak

você sabe o que ele não suporta? eu vi teste pode ser em JSON duas vezes e ele aceita 2ª ocorrência
tooptoop4

@ tooptoop4 seria necessário escrever um decodificador personalizado para avisar sobre campos duplicados - adicionando ineficiências ao decodificador - tudo para lidar com um cenário que nunca aconteceria. Nenhum codificador JSON padrão jamais produziria campos duplicados.
colm.anseo 28/01

20

Achei o exemplo a seguir dos documentos realmente útil (fonte aqui ).

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

A chave aqui é que o OP estava tentando decodificar

type test_struct struct {
    Test string
}

... nesse caso, soltaríamos o const jsonStreame substituiríamos a Messagestruct pelo 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)
    }
}

Atualização : Eu também acrescentaria que este post também fornece ótimos dados sobre como responder com JSON. O autor explica struct tags, do qual eu não estava ciente.

Desde JSON normalmente não parecer {"Test": "test", "SomeKey": "SomeVal"}, mas sim {"test": "test", "somekey": "some value"}, você pode reestruturar a sua estrutura como esta:

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

... e agora seu manipulador analisará JSON usando "some-key" em vez de "SomeKey" (que você usará internamente).


1
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)
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.