在Go中处理JSON Post请求

huangapple go评论81阅读模式
英文:

Handling JSON Post Request in Go

问题

所以我有以下的代码,看起来非常hacky,我一直在想Go是否有比这更好设计的库,但我找不到Go处理POST请求JSON数据的例子。它们都是表单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,并在这里提到以便其他人可以找到它。)

英文:

So I have the following, which seems incredibly hacky, and I've been thinking to myself that Go has better designed libraries than this, but I can't find an example of Go handling a POST request of JSON data. They are all form POSTs.

Here is an example request: curl -X POST -d "{\"test\": \"that\"}" http://localhost:8082/test

And here is the code, with the logs embedded:

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

There's got to be a better way, right? I'm just stumped in finding what the best practice could be.

(Go is also known as Golang to the search engines, and mentioned here so others can find it.)

答案1

得分: 466

请使用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)
}
英文:

Please use json.Decoder instead of 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)
}

答案2

得分: 110

你需要从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))
}
英文:

You need to read from req.Body. The ParseForm method is reading from the req.Body and then parsing it in standard HTTP encoded format. What you want is to read the body and parse it in JSON format.

Here's your code updated.

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

答案3

得分: 99

有两个原因说明为什么应该优先使用json.Decoder而不是json.Unmarshal - 这些原因在2013年最受欢迎的答案中没有提到:

  1. 2018年2月,go 1.10引入了一个新的方法json.Decoder.DisallowUnknownFields(),它解决了检测不需要的JSON输入的问题。
  2. req.Body已经是一个io.Reader。如果将其整个内容读取出来,然后执行json.Unmarshal,如果流是一个10MB的无效JSON块,那么会浪费资源。使用json.Decoder解析请求体,因为它是以流的方式进行解析,如果遇到无效的JSON,会触发早期的解析错误。实时处理I/O流是首选的go方式。

针对一些用户关于检测错误用户输入的评论:

为了强制执行必填字段和其他卫生检查,请尝试:

d := json.NewDecoder(req.Body)
d.DisallowUnknownFields() // 捕获不需要的字段

// 匿名结构体类型:方便一次性使用
t := struct {
    Test *string `json:"test"` // 使用指针以便我们可以测试字段是否缺失
}{}

err := d.Decode(&t)
if err != nil {
    // 错误的JSON或无法识别的JSON字段
    http.Error(rw, err.Error(), http.StatusBadRequest)
    return
}

if t.Test == nil {
    http.Error(rw, "缺少字段 'test' from JSON object", http.StatusBadRequest)
    return
}

// 可选的额外检查
if d.More() {
    http.Error(rw, "JSON对象后面有多余的数据", http.StatusBadRequest)
    return
}

// 得到了我们期望的输入:既不多也不少
log.Println(*t.Test)

Playground

典型输出:

$ curl -X POST -d "{}" http://localhost:8082/strict_test

预期的json字段 'test'
    
$ curl -X POST -d "{\"Test\":\"maybe?\",\"Unwanted\":\"1\"}" http://localhost:8082/strict_test

json: 未知字段 "Unwanted"

$ curl -X POST -d "{\"Test\":\"oops\"}g4rB4g3@#$%^&*\"" http://localhost:8082/strict_test

JSON后面有多余的数据

$ curl -X POST -d "{\"Test\":\"Works\"}" http://localhost:8082/strict_test 

log: 2019/03/07 16:03:13 Works
英文:

There are two reasons why json.Decoder should be preferred over json.Unmarshal - that are not addressed in the most popular answer from 2013:

  1. February 2018, go 1.10 introduced a new method json.Decoder.DisallowUnknownFields() which addresses the concern of detecting unwanted JSON-input
  2. req.Body is already an io.Reader. Reading its entire contents and then performing json.Unmarshal wastes resources if the stream was, say a 10MB block of invalid JSON. Parsing the request body, with json.Decoder, as it streams in would trigger an early parse error if invalid JSON was encountered. Processing I/O streams in realtime is the preferred go-way.

Addressing some of the user comments about detecting bad user input:

To enforce mandatory fields, and other sanitation checks, try:

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)

Playground

Typical output:

$ 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

答案4

得分: 82

我为这个确切的问题而苦恼了很久。我的JSON编组器和解组器没有填充我的Go结构体。然后我在https://eager.io/blog/go-and-json找到了解决方案:

> “与Go中的所有结构体一样,重要的是要记住,只有首字母大写的字段对外部程序(如JSON编组器)可见。”

之后,我的编组器和解组器完美地工作了!

英文:

I was driving myself crazy with this exact problem. My JSON Marshaller and Unmarshaller were not populating my Go struct. Then I found the solution at https://eager.io/blog/go-and-json:

> "As with all structs in Go, it’s important to remember that only
> fields with a capital first letter are visible to external programs
> like the JSON Marshaller."

After that, my Marshaller and Unmarshaller worked perfectly!

答案5

得分: 22

我从文档中找到了以下示例,非常有帮助(源代码在这里)。

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"来解析JSON,而不是"SomeKey"(在内部使用)。

英文:

I found the following example from the docs really helpful (source here).

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

The key here being that the OP was looking to decode

type test_struct struct {
    Test string
}

...in which case we would drop the const jsonStream, and replace the Message struct with the 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)
	}
}

Update: I would also add that this post provides some great data about responding with JSON as well. The author explains struct tags, which I was not aware of.

Since JSON does not normally look like {"Test": "test", "SomeKey": "SomeVal"}, but rather {"test": "test", "somekey": "some value"}, you can restructure your struct like this:

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

...and now your handler will parse JSON using "some-key" as opposed to "SomeKey" (which you will be using internally).

答案6

得分: 7

我喜欢在本地定义自定义结构体。所以:

// 我的处理函数
func addImage(w http.ResponseWriter, r *http.Request) {

    // 定义自定义类型
    type Input struct {
        Url        string  `json:"url"`
        Name       string  `json:"name"`
        Priority   int8    `json:"priority"`
    }

    // 定义一个变量
    var input Input

    // 解码输入或返回错误
    err := json.NewDecoder(r.Body).Decode(&input)
    if err != nil {
        w.WriteHeader(400)
        fmt.Fprintf(w, "解码错误!请检查您的JSON格式。")
        return
    }

    // 打印用户输入
    fmt.Fprintf(w, "输入的名称:%s", input.Name)

}
英文:

I like to define custom structs locally. So:

// my handler func
func addImage(w http.ResponseWriter, r *http.Request) {

    // define custom type
	type Input struct {
		Url        string  `json:"url"`
		Name       string  `json:"name"`
		Priority   int8    `json:"priority"`
	}

    // define a var 
	var input Input

	// decode input or return error
	err := json.NewDecoder(r.Body).Decode(&input)
	if err != nil {
		w.WriteHeader(400)
		fmt.Fprintf(w, "Decode error! please check your JSON formating.")
		return
	}

    // print user inputs
	fmt.Fprintf(w, "Inputed name: %s", input.Name)

}

答案7

得分: 4

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)

}

英文:
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)
}

答案8

得分: 0

之前的ReadAll函数是ioutil包的一部分,但现在已经被弃用。但是现在io包本身有ReadAll函数。

type test struct {
  Test string `json:"test"`
}

func test(w http.ResponseWriter, req *http.Request) {
  var t test_struct
  body, _ := io.ReadAll(req.Body)
  json.Unmarshal(body, &t)
  fmt.Println(t)
}
英文:

Earlier ReadAll func was part of ioutil package, but currently it got deprecated. But now the io package itself has the ReadAll func.

type test struct {
  Test string `json:"test"`
}

func test(w http.ResponseWriter, req *http.Request) {
  var t test_struct
  body, _ := io.ReadAll(req.Body)
  json.Unmarshal(body, &t)
  fmt.Println(t)
}

答案9

得分: 0

type PostMessage struct {
	Action string
}

func Execute(w http.ResponseWriter, r *http.Request) {

	var t PostMessage

	err := json.NewDecoder(r.Body).Decode(&t)
	if err != nil {
		fmt.Println(err)
	}

	fmt.Println(t.Action)
}
英文:

Shorter code:

type PostMessage struct {
	Action string
}

func Execute(w http.ResponseWriter, r *http.Request) {

	var t PostMessage

	err := json.NewDecoder(r.Body).Decode(&t)
	if err != nil {
		fmt.Println(err)
	}

	fmt.Println(t.Action)
}

huangapple
  • 本文由 发表于 2013年3月28日 09:16:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/15672556.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定