GoでのJSON Postリクエストの処理


250

だから私は信じられないほどハックに思える次のものを持っています、そして私はGoがこれよりも優れたライブラリを設計したと私は考えていましたが、JSONデータのPOSTリクエストを処理するGoの例を見つけることができません。それらはすべてフォームPOSTです。

次にリクエストの例を示します。 curl -X POST -d "{\"test\": \"that\"}" http://localhost:8082/test

そしてここにコードがあり、ログが埋め込まれています:

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

もっと良い方法があるはずですよね?私はベストプラクティスが何であるかを見つけるのに困惑しています。

(Goは検索エンジンではGolangとも呼ばれ、他の人が見つけられるようにここで言及されています。)


3
を使用した場合curl -X POST -H 'Content-Type: application/json' -d "{\"test\": \"that\"}"req.Form["test"]戻ります"that"
Vinicius

@Viniciusはこれの証拠はありますか?
diralik

回答:


388

json.Decoder代わりに使用してください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
理由を教えてください。
Ryan Bigg 2013

86
まず、すべてを自分でバッファにロードする必要はなく、ストリームを処理できるように見えます。(私は別のJoe BTWです)
Joe

7
この場合、適切なエラー処理はどのようになるのでしょうか。無効なjsonでパニックを起こすのは良い考えではないと思います。
codepushr 2014年

15
私はあなたがする必要があるとは思いませんdefer req.Body.Close():「サーバーはリクエストボディを閉じます。ServeHTTPハンドラーは閉じる必要はありません。」また、ドキュメントから@thisisnotabusに回答する場合:「サーバーリクエストの場合、リクエストボディは常に非nilですが、ボディが存在しない場合はすぐにEOFを返します」golang.org/pkg/net/http/#Request
Drew LeSueur

22
を使用しないことをお勧めしjson.Decoderます。これは、単一のオブジェクトではなく、JSONオブジェクトのストリームを対象としています。オブジェクト全体をメモリに読み込むため、単一のJSONオブジェクトの方が効率的ではありません。オブジェクトの後にゴミが含まれていても文句を言わないという欠点があります。いくつかの要因によってはjson.Decoder、本文を完全に読み取れない可能性があり、接続は再利用できません。
Kale B

85

から読む必要がありますreq.BodyParseFormこの方法は、から読み取ってreq.Body、その後、標準HTTP符号化された形式でそれを解析します。必要なのは、本文を読み取ってJSON形式で解析することです。

これが更新されたコードです。

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

ありがとう!私は今どこが間違っているのかわかります。req.ParseForm()この問題を解決しようとする以前の試みで私が行っていたを呼び出すと、を読んでみる前にreq.Body、本文がクリアされたように見えunexpected end of JSON inputUnmarshal(少なくとも1.0.2で)移動するとスローされます
TomJ

1
@Daniel:curl -X POST -d "{\" tes \ ":\" that \ "}" localhost:8082 / testを実行すると、log.Println(t.Test)が空を返します。どうして ?あるいは、他のJSONを投稿すると、空を返します
Somesh

POSTリクエストが間違っています。tes!= test。5年前のことを感謝します:/
Rambatino

これは素晴らしいシンプルな例です!
15412:

これは良いアドバイスですが、明確に言うと、の使用に関する回答json.NewDecoder(req.Body)も正しいです。
リッチ

59

私はこの正確な問題に夢中になっていた。私のJSON MarshallerとUnmarshallerは私のGo構造体にデータを入れていませんでした。それから私はhttps://eager.io/blog/go-and-jsonで解決策を見つけました:

「Goのすべての構造体と同様に、最初の文字が大文字のフィールドのみが、JSONマーシャラーなどの外部プログラムから見えることを覚えておくことが重要です。」

その後、私のマーシャラーとアンマーシャラーは完全に機能しました!


リンクからいくつかのスニペットを含めてください。廃止された場合、例は失われます。
030

47

なぜ二つの理由がありますjson.Decoder好まれるべきjson.Unmarshal- 2013年で最も人気のある答えで対処されていません。

  1. 2018年2月、 不要なJSON入力の検出の懸念に対処go 1.10する新しいメソッドjson.Decoder.DisallowUnknownFields()を導入
  2. req.Bodyはすでにio.Readerです。コンテンツ全体を読み取ってから実行json.Unmarshalすると、ストリームが無効なJSONの10MBブロックである場合、リソースが無駄になります。リクエストボディをで解析するとjson.Decoder、無効なJSONが検出された場合に、それがストリーミングされるときに早期解析エラーがトリガーされます。I / Oストリームをリアルタイムで処理することをお勧めします

不正なユーザー入力の検出に関するユーザーコメントの一部に対処します。

必須フィールドやその他の衛生チェックを実施するには、次のことを試してください。

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)

遊び場

典型的な出力:

$ 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
何か悪いことを述べるのではなく、意見を説明していただきありがとうございます
Fjolnir Dvorak

それが処理しないものを知っていますか?私はテストが二回JSONにすることができましたし、それは第二の発生を受け入れる
tooptoop4

@ tooptoop4重複フィールドについて警告するカスタムデコーダーを作成する必要があります-デコーダーに非効率性を追加します-決して発生しないシナリオを処理するために。標準のJSONエンコーダーが重複フィールドを生成することはありません。
colm.anseo

20

私はドキュメントから次の例が本当に役立つことがわかりました(ソースはこちら)。

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

ここで重要なのは、OPがデコードしようとしていたことです。

type test_struct struct {
    Test string
}

...この場合、を削除しconst jsonStreamMessage構造体をに置き換え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)
    }
}

更新この投稿にはJSONでの応答に関する優れたデータも含まれていることも付け加えておきます。著者はstruct tags私が知らなかったを説明します。

JSONは通常のように見えるの{"Test": "test", "SomeKey": "SomeVal"}ではなく{"test": "test", "somekey": "some value"}、なので、次のように構造体を再構築できます。

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

...これで、ハンドラーは「SomeKey」(内部で使用する)ではなく、「some-key」を使用してJSONを解析します。


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)
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.