Menangani Permintaan Posting JSON dalam Go

250

Jadi saya memiliki yang berikut ini, yang tampaknya sangat tidak rapi, dan saya telah berpikir pada diri saya sendiri bahwa Go memiliki perpustakaan yang dirancang lebih baik daripada ini, tetapi saya tidak dapat menemukan contoh Go menangani permintaan POST data JSON. Mereka semua membentuk POST.

Berikut ini contoh permintaan: curl -X POST -d "{\"test\": \"that\"}" http://localhost:8082/test

Dan ini kodenya, dengan log yang disematkan:

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

Pasti ada cara yang lebih baik, bukan? Saya hanya bingung menemukan apa yang bisa menjadi praktik terbaik.

(Go juga dikenal sebagai Golang ke mesin pencari, dan disebutkan di sini sehingga orang lain dapat menemukannya.)

TomJ
sumber
3
jika Anda menggunakan curl -X POST -H 'Content-Type: application/json' -d "{\"test\": \"that\"}", maka req.Form["test"]harus kembali"that"
Vinicius
@Vinicius apakah ada bukti tentang ini?
diralik

Jawaban:

388

Silakan gunakan json.Decodersebagai ganti 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)
}
Joe
sumber
79
Bisakah Anda jelaskan mengapa?
Ryan Bigg
86
Sebagai permulaan, sepertinya ini bisa menangani aliran daripada mengharuskan Anda untuk memuat semuanya ke dalam buffer sendiri. (Saya berbeda Joe BTW)
Joe
7
Saya ingin tahu bagaimana penanganan kesalahan yang tepat akan terlihat seperti dalam kasus ini. Saya pikir itu bukan ide yang baik untuk panik pada json yang tidak valid.
codepushr
15
Saya tidak berpikir Anda perlu defer req.Body.Close()Dari dokumen: "Server akan menutup tubuh permintaan. ServeHTTP Handler tidak perlu." Juga untuk menjawab @ thisisnotabus, dari dokumen: "Untuk permintaan server, Badan Permintaan selalu non-nil tetapi akan segera mengembalikan EOF ketika tidak ada badan" golang.org/pkg/net/http/#Request
Drew LeSueur
22
Saya sarankan tidak menggunakan json.Decoder. Ini dimaksudkan untuk stream objek JSON, bukan objek tunggal. Ini tidak lebih efisien untuk objek JSON tunggal karena membaca seluruh objek menjadi memori. Ini memiliki kelemahan bahwa jika sampah dimasukkan setelah objek tidak akan mengeluh. Tergantung pada beberapa faktor, json.Decodermungkin tubuh tidak sepenuhnya terbaca dan koneksi tidak akan dapat digunakan kembali.
Kale B
85

Anda perlu membaca req.Body. The ParseFormMetode membaca dari req.Bodydan kemudian parsing itu dalam standar HTTP format yang disandikan. Yang Anda inginkan adalah membaca isi dan menguraikannya dalam format JSON.

Ini kode Anda yang diperbarui.

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))
}
Daniel
sumber
Terima kasih! Saya melihat di mana saya salah sekarang. Jika Anda menelepon req.ParseForm(), yang saya lakukan dalam upaya sebelumnya untuk mencoba memecahkan masalah ini, sebelum Anda mencoba dan membaca req.Body, sepertinya membersihkan tubuh dan unexpected end of JSON inputdibuang ketika Anda pergi ke Unmarshal(setidaknya dalam 1.0.2)
TomJ
1
@Daniel: Ketika saya melakukan curl -X POST -d "{\" tes \ ": \" that \ "}" localhost: 8082 / test , log.Println (t.Test) kembali kosong. Mengapa Atau dalam hal ini jika memposting JSON lain, itu kembali kosong
Somesh
Permintaan POST Anda salah. tes! = tes. Hargai itu 5 tahun yang lalu: /
Rambatino
Ini adalah contoh sederhana yang bagus!
15412
Ini adalah saran yang bagus, tetapi untuk menjadi jelas, jawaban yang mengacu pada penggunaan json.NewDecoder(req.Body)juga benar.
Kaya
59

Saya membuat diri saya gila dengan masalah ini. JSON Marshaller dan Unmarshaller saya tidak mengisi Go struct saya. Kemudian saya menemukan solusinya di https://eager.io/blog/go-and-json :

"Seperti semua struct di Go, penting untuk diingat bahwa hanya bidang dengan huruf kapital pertama yang dapat dilihat oleh program eksternal seperti JSON Marshaller."

Setelah itu, Marshaller dan Unmarshaller saya bekerja dengan sempurna!

Steve Stilson
sumber
Harap sertakan beberapa cuplikan dari tautan. Jika sudah ditinggalkan contoh akan hilang.
030
47

Ada dua alasan mengapa json.Decoderlebih disukai daripada json.Unmarshal- yang tidak dibahas dalam jawaban paling populer dari 2013:

  1. Februari 2018, go 1.10memperkenalkan metode baru json.Decoder.DisallowUnknownFields () yang membahas masalah mendeteksi input JSON yang tidak diinginkan
  2. req.Bodysudah menjadi io.Reader. Membaca seluruh isinya dan kemudian melakukan json.Unmarshalpemborosan sumber daya jika alirannya, katakanlah blok 10MB JSON tidak valid. Mem-parsing badan permintaan, dengan json.Decoder, saat streaming masuk akan memicu kesalahan parse awal jika JSON tidak valid ditemukan. Pengolahan I / O stream secara realtime adalah disukai go-cara .

Mengatasi beberapa komentar pengguna tentang mendeteksi input pengguna yang buruk:

Untuk menegakkan bidang wajib, dan pemeriksaan sanitasi lainnya, cobalah:

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)

Tempat bermain

Output khas:

$ 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
colm.anseo
sumber
6
Terima kasih telah menjelaskan pendapat dan bukan hanya menyatakan bahwa ada sesuatu yang buruk
Fjolnir Dvorak
apakah Anda tahu apa yang tidak ditangani? saya melihat Test dapat di json dua kali dan menerima kemunculan ke-2
tooptoop4
@ Tooptoop4 kita perlu menulis dekoder khusus untuk memperingatkan tentang bidang duplikat - menambahkan inefisiensi ke dekoder - semua untuk menangani skenario yang tidak akan pernah terjadi. Tidak ada encoder JSON standar yang akan menghasilkan bidang duplikat.
colm.anseo
20

Saya menemukan contoh berikut dari dokumen sangat membantu (sumber di sini ).

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

Kuncinya di sini adalah bahwa OP ingin memecahkan kode

type test_struct struct {
    Test string
}

... dalam hal ini kita akan menjatuhkan const jsonStream, dan mengganti Messagestruct dengan 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)
    }
}

Pembaruan : Saya juga akan menambahkan bahwa posting ini menyediakan beberapa data hebat tentang menanggapi dengan JSON juga. Penulis menjelaskan struct tags, yang tidak saya sadari.

Karena JSON biasanya tidak terlihat seperti {"Test": "test", "SomeKey": "SomeVal"}, tetapi {"test": "test", "somekey": "some value"}Anda dapat merestrukturisasi struct Anda seperti ini:

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

... dan sekarang pawang Anda akan menguraikan JSON menggunakan "some-key" sebagai kebalikan dari "SomeKey" (yang akan Anda gunakan secara internal).

JohnnyCoder
sumber
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)
}
Angelica Payawal
sumber