在Go中处理JSON发布请求


250

因此,我得到了以下内容,这些内容似乎非常难以破解,并且我一直在想自己Go的库设计得比此更好,但是我找不到Go处理JSON数据POST请求的示例。它们都是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 '17

Answers:


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

86
首先,看起来它可以处理流,而不需要您自己将其全部加载到缓冲区中。(我是另一位乔BTW)

7
我不知道在这种情况下如何进行正确的错误处理。我认为恐慌无效的json不是一个好主意。
codepushr 2014年

15
我认为您不需要defer req.Body.Close()从文档中:“服务器将关闭请求正文。ServeHTTP处理程序不需要。” 还可以从文档中回答@thisisnotabus:“对于服务器请求,请求正文始终为非零,但在不存在正文时将立即返回EOF” golang.org/pkg/net/http/#Request
Drew LeSueur

22
我建议不要使用json.Decoder。它用于JSON对象流,而不是单个对象。对于单个JSON对象,它效率不高,因为它将整个对象读入内存。不利的一面是,如果在对象之后包含垃圾,它将不会抱怨。根据一些因素,json.Decoder可能无法完全读取正文,并且该连接不符合重用条件。
羽衣甘蓝B

85

您需要阅读req.Body。该ParseForm方法是从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 input在您去时被抛出Unmarshal(至少在1.0.2中)
TomJ

1
@Daniel:当我卷曲时-X POST -d“ {\” tes \“:\” that \“}” localhost:8082 / test,log.Println(t.Test)返回空。为什么呢 或者就此而言,如果张贴任何其他JSON,它返回空
Somesh

您的POST请求有误。tes!=测试。感谢5年前:/
Rambatino,

这是一个很好的简单示例!
15412s

这是一个很好的建议,但要明确一点,有关使用的答案json.NewDecoder(req.Body)也是正确的。
Rich

59

我对这个确切的问题发疯了。我的JSON Marshaller和Unmarshaller没有填充我的Go结构。然后我在https://eager.io/blog/go-and-json找到了解决方案:

“与Go中的所有结构一样,重要的是要记住,只有带有大写首字母的字段才对JSON Marshaller这样的外部程序可见。”

在那之后,我的Marshaller和Unmarshaller表现完美!


请在链接中包含一些摘要。如果不赞成使用,则示例将丢失。
030

47

有两点原因json.Decoder值得我们优先考虑json.Unmarshal-2013年最受欢迎的答案中没有解决这些问题:

  1. 2018年2月,go 1.10引入了一种新方法json.Decoder.DisallowUnknownFields() ,该方法解决了检测到不需要的JSON输入的问题
  2. req.Body已经是io.Readerjson.Unmarshal如果流是一个10MB的无效JSON块,则读取其全部内容然后执行操作会浪费资源。解析请求身体,用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

你知道它不处理吗?我看到Test可以在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 jsonStream,并将Message结构替换为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"`
}

...现在您的处理程序将使用“ some-key”而不是“ SomeKey”(您将在内部使用)解析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)
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.