Xử lý yêu cầu bài viết JSON trong Go


250

Vì vậy, tôi có những điều sau đây, có vẻ như rất khó tin và tôi đã tự nghĩ rằng Go có các thư viện được thiết kế tốt hơn thế này, nhưng tôi không thể tìm thấy một ví dụ về việc xử lý yêu cầu POST của dữ liệu JSON. Chúng đều là dạng POST.

Đây là một yêu cầu ví dụ: curl -X POST -d "{\"test\": \"that\"}" http://localhost:8082/test

Và đây là mã, với các bản ghi được nhúng:

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

Có một cách tốt hơn, phải không? Tôi chỉ bối rối trong việc tìm kiếm những gì thực hành tốt nhất có thể.

(Go còn được gọi là Golang cho các công cụ tìm kiếm và được đề cập ở đây để những người khác có thể tìm thấy nó.)


3
nếu bạn sử dụng curl -X POST -H 'Content-Type: application/json' -d "{\"test\": \"that\"}"thì req.Form["test"]nên trả lại"that"
Vinicius

@Vinicius có bằng chứng nào về điều này không?
diralik

Câu trả lời:


388

Vui lòng sử dụng json.Decoderthay vì 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
Bạn có thể vui lòng giải thích tại sao?
Ryan Bigg

86
Để bắt đầu, có vẻ như điều này có thể xử lý một luồng thay vì cần bạn tự tải tất cả vào bộ đệm. (Tôi là Joe BTW khác)
Joe

7
Tôi tự hỏi làm thế nào xử lý lỗi thích hợp sẽ trông như thế nào trong trường hợp này. Tôi không nghĩ rằng đó là một ý tưởng tốt để hoảng loạn trên một json không hợp lệ.
codepushr

15
Tôi không nghĩ bạn cần defer req.Body.Close()Từ tài liệu: "Máy chủ sẽ đóng phần yêu cầu. Trình xử lý ServeHTTP không cần." Ngoài ra để trả lời @thisisnotabus, từ các tài liệu: "Đối với các yêu cầu máy chủ, Cơ quan yêu cầu luôn không phải là con số không nhưng sẽ trả lại EOF ngay lập tức khi không có xác chết" golang.org/pkg/net/http/#Request
Drew LeSueur

22
Tôi sẽ đề nghị không sử dụng json.Decoder. Nó được dành cho các luồng của các đối tượng JSON, không phải là một đối tượng. Nó không hiệu quả hơn cho một đối tượng JSON vì nó đọc toàn bộ đối tượng vào bộ nhớ. Nó có một nhược điểm là nếu rác được bao gồm sau đối tượng thì nó sẽ không khiếu nại. Tùy thuộc vào một vài yếu tố, json.Decodercó thể không đọc toàn bộ phần thân và kết nối sẽ không đủ điều kiện để sử dụng lại.
Kale B

85

Bạn cần đọc từ req.Body. Các ParseFormphương pháp được đọc từ req.Bodyvà sau đó phân tích nó trong định dạng mã hóa HTTP chuẩn. Những gì bạn muốn là đọc phần thân và phân tích nó theo định dạng JSON.

Đây là mã của bạn được cập nhật.

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

Cảm ơn! Tôi thấy nơi tôi đã đi sai bây giờ. Nếu bạn gọi req.ParseForm(), điều mà tôi đang làm trong những nỗ lực trước đó để cố gắng giải quyết vấn đề này, trước khi bạn thử và đọc req.Body, nó dường như xóa cơ thể ra và unexpected end of JSON inputbị ném khi bạn đi đến Unmarshal(ít nhất là trong 1.0.2)
TomJ

1
@Daniel: Khi tôi thực hiện curl -X POST -d "{\" tes \ ": \" that \ "}" localhost: 8082 / test , log.Println (t.Test) trả về sản phẩm nào. Tại sao ? Hoặc đối với vấn đề đó nếu đăng bất kỳ JSON nào khác, nó sẽ trả về sản phẩm nào đó
vào

Yêu cầu POST của bạn sai. tes! = kiểm tra. Đánh giá cao đó là 5 năm trước: /
Rambatino

Đây là một ví dụ đơn giản tốt đẹp!
15412s

Đây là lời khuyên tốt, nhưng để rõ ràng, các câu trả lời đề cập đến việc sử dụng json.NewDecoder(req.Body)cũng chính xác.
Giàu

59

Tôi đã tự làm mình phát điên với vấn đề chính xác này. JSON Marshaller và Unmarshaller của tôi không tạo ra cấu trúc Go của tôi. Sau đó, tôi tìm thấy giải pháp tại https://eager.io/blog/go-and-json :

"Như với tất cả các cấu trúc trong Go, điều quan trọng cần nhớ là chỉ các trường có chữ cái đầu tiên được hiển thị cho các chương trình bên ngoài như JSON Marshaller."

Sau đó, Marshaller và Unmarshaller của tôi đã hoạt động hoàn hảo!


Vui lòng bao gồm một số đoạn từ liên kết. Nếu nó bị phản đối, các ví dụ sẽ bị mất.
030

47

Có hai lý do tại sao json.Decodernên được ưu tiên hơn json.Unmarshal- không được đề cập trong câu trả lời phổ biến nhất từ ​​năm 2013:

  1. Tháng 2 năm 2018, đã go 1.10giới thiệu một phương thức mới json.Decoder.DisallowUn UnknownFields () nhằm giải quyết mối quan tâm phát hiện đầu vào JSON không mong muốn
  2. req.Bodyđã là một io.Reader. Đọc toàn bộ nội dung của nó và sau đó thực hiện json.Unmarshallãng phí tài nguyên nếu luồng là, giả sử một khối 10 MB JSON không hợp lệ. Phân tích phần thân yêu cầu, với json.Decoder, khi nó truyền vào sẽ gây ra lỗi phân tích cú pháp sớm nếu gặp phải JSON không hợp lệ. Xử lý I / O suối trong thời gian thực là ưa thích đi chiều .

Giải quyết một số ý kiến ​​của người dùng về việc phát hiện đầu vào của người dùng xấu:

Để thực thi các trường bắt buộc và kiểm tra vệ sinh khác, hãy thử:

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)

Sân chơi

Sản lượng tiêu biểu:

$ 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
Cảm ơn bạn đã giải thích ý kiến ​​thay vì chỉ nói rằng có điều gì đó không tốt
Fjolnir Dvorak

Bạn có biết những gì nó không xử lý? tôi thấy Test có thể ở json hai lần và nó chấp nhận lần xuất hiện thứ 2
tooptoop4

@ tooptoop4 người ta sẽ cần phải viết một bộ giải mã tùy chỉnh để cảnh báo về các trường trùng lặp - thêm sự thiếu hiệu quả vào bộ giải mã - tất cả để xử lý một kịch bản sẽ không bao giờ xảy ra. Không có bộ mã hóa JSON tiêu chuẩn nào tạo ra các trường trùng lặp.
colm.anseo

20

Tôi tìm thấy ví dụ sau từ các tài liệu thực sự hữu ích (nguồn ở đây ).

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

Chìa khóa ở đây là OP đang tìm cách giải mã

type test_struct struct {
    Test string
}

... trong trường hợp nào chúng ta sẽ bỏ const jsonStreamvà thay thế Messagestruct bằng 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)
    }
}

Cập nhật : Tôi cũng sẽ thêm rằng bài đăng này cũng cung cấp một số dữ liệu tuyệt vời về việc phản hồi với JSON. Tác giả giải thích struct tags, điều mà tôi không nhận thức được.

Vì JSON thường không giống như {"Test": "test", "SomeKey": "SomeVal"}, nhưng thay vào đó {"test": "test", "somekey": "some value"}, bạn có thể cấu trúc lại cấu trúc của mình như thế này:

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

... và bây giờ trình xử lý của bạn sẽ phân tích cú pháp JSON bằng cách sử dụng "some-key" trái ngược với "someKey" (mà bạn sẽ sử dụng nội bộ).


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)
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.