Gestire la richiesta JSON Post in Go


250

Quindi ho il seguente, che sembra incredibilmente confuso, e ho pensato a me stesso che Go ha librerie progettate meglio di così, ma non riesco a trovare un esempio di Go che gestisce una richiesta POST di dati JSON. Sono tutti post POST.

Ecco una richiesta di esempio: curl -X POST -d "{\"test\": \"that\"}" http://localhost:8082/test

Ed ecco il codice, con i log incorporati:

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

Deve esserci un modo migliore, giusto? Sono solo perplesso nel trovare quale potrebbe essere la migliore pratica.

(Go è anche noto come Golang per i motori di ricerca, e menzionato qui in modo che altri possano trovarlo.)


3
se usi curl -X POST -H 'Content-Type: application/json' -d "{\"test\": \"that\"}", allora req.Form["test"]dovrebbe tornare"that"
Vinicius il

@Vinicius ci sono prove di questo?
Diralik,

Risposte:


388

Si prega di utilizzare json.Decoderinvece di 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
Potresti spiegare perché?
Ryan Bigg,

86
Per cominciare, sembra che questo possa gestire un flusso piuttosto che aver bisogno di caricare tutto da solo in un buffer. (Sono un altro Joe BTW)
Joe

7
Mi chiedo come sarebbe la corretta gestione degli errori in questo caso. Non credo sia una buona idea farsi prendere dal panico su un json non valido.
codepushr

15
Non credo che sia necessario defer req.Body.Close()Dal documento: "Il server chiuderà il corpo della richiesta. Il gestore ServeHTTP non è necessario." Anche per rispondere a @thisisnotabus, dai documenti: "Per le richieste del server il corpo della richiesta è sempre diverso da zero ma restituirà immediatamente EOF quando non è presente alcun corpo" golang.org/pkg/net/http/#Request
Drew LeSueur

22
Suggerirei di non usare json.Decoder. È destinato a flussi di oggetti JSON, non a un singolo oggetto. Non è più efficiente per un singolo oggetto JSON poiché legge l'intero oggetto in memoria. Ha un aspetto negativo che se la spazzatura è inclusa dopo l'oggetto non si lamenterà. A seconda di alcuni fattori, json.Decoderpotrebbe non essere possibile leggere completamente il corpo e la connessione non sarà idonea al riutilizzo.
Kale B,

85

Devi leggere da req.Body. Il ParseFormmetodo sta leggendo da req.Bodye quindi analizzandolo nel formato codificato HTTP standard. Quello che vuoi è leggere il corpo e analizzarlo in formato JSON.

Ecco il tuo codice aggiornato.

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

Grazie! Vedo dove stavo andando storto ora. Se chiami req.ParseForm(), cosa che stavo facendo in precedenti tentativi di provare a risolvere questo problema, prima di provare a leggere req.Body, sembra che cancelli il corpo e unexpected end of JSON inputvenga lanciato quando vai a Unmarshal(almeno in 1.0.2)
TomJ

1
@Daniel: quando faccio arricciare -X POST -d "{\" tes \ ": \" that \ "}" localhost: 8082 / test , log.Println (t.Test) restituisce vuoto. Perché ? O del resto se pubblica un altro JSON, torna vuoto
Somesh,

La tua richiesta POST è errata. tes! = test. Apprezzo che fosse 5 anni fa: /
Rambatino

Questo è un bell'esempio semplice!
15412s

Questo è un buon consiglio, ma per essere chiari, anche le risposte che si riferiscono all'uso di json.NewDecoder(req.Body)sono corrette.
Rich

59

Mi stavo facendo impazzire per questo esatto problema. Il mio JSON Marshaller e Unmarshaller non stavano popolando la mia struttura Go. Quindi ho trovato la soluzione su https://eager.io/blog/go-and-json :

"Come per tutte le strutture di Go, è importante ricordare che solo i campi con la prima lettera maiuscola sono visibili a programmi esterni come JSON Marshaller."

Dopodiché, il mio Marshaller e Unmarshaller hanno funzionato perfettamente!


Si prega di includere alcuni frammenti dal collegamento. Se viene deprecato, gli esempi andranno persi.
030

47

Ci sono due motivi per cui json.Decoderdovrebbe essere preferito rispetto a json.Unmarshal- che non sono affrontati nella risposta più popolare del 2013:

  1. Febbraio 2018, ha go 1.10introdotto un nuovo metodo json.Decoder.DisallowUnknownFields () che affronta la preoccupazione di rilevare input JSON indesiderati
  2. req.Bodyè già un io.Reader. Leggendo il suo intero contenuto e quindi eseguendo json.Unmarshalrisorse di spreco se lo streaming fosse, diciamo un blocco di 10 MB di JSON non valido. L'analisi del corpo della richiesta, con json.Decoder, mentre viene eseguito in streaming , innescherebbe un errore di analisi precoce se si rilevasse un JSON non valido. L'elaborazione dei flussi di I / O in tempo reale è la strada da percorrere preferita .

Rispondere ad alcuni dei commenti degli utenti sul rilevamento di input utente errati:

Per applicare campi obbligatori e altri controlli di igiene, prova:

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)

Terreno di gioco

Uscita tipica:

$ 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
Grazie per aver spiegato le opinioni invece di dichiarare che qualcosa non va
Fjolnir Dvorak

sai cosa non gestisce? ho visto Test può essere due volte in json e accetta la seconda occorrenza
tooptoop4

@ tooptoop4 uno avrebbe bisogno di scrivere un decodificatore personalizzato per avvertire di campi duplicati - aggiungendo inefficienze al decodificatore - tutto per gestire uno scenario che non sarebbe mai accaduto. Nessun codificatore JSON standard produrrebbe mai campi duplicati.
colm.anseo,

20

Ho trovato molto utile il seguente esempio dai documenti (fonte qui ).

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

La chiave qui è che l'OP stava cercando di decodificare

type test_struct struct {
    Test string
}

... nel qual caso elimineremo il const jsonStream, e sostituiremo la Messagestruttura con 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)
    }
}

Aggiornamento : Vorrei anche aggiungere che questo post fornisce anche alcuni ottimi dati sulla risposta con JSON. L'autore spiega struct tags, di cui non ero a conoscenza.

Dal momento che JSON normalmente non assomiglia {"Test": "test", "SomeKey": "SomeVal"}, ma piuttosto {"test": "test", "somekey": "some value"}, è possibile ristrutturare la struttura in questo modo:

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

... e ora il tuo gestore analizzerà JSON usando "some-key" anziché "SomeKey" (che userai 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)
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.