英文:
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年最受欢迎的答案中没有提到:
- 2018年2月,
go 1.10
引入了一个新的方法json.Decoder.DisallowUnknownFields(),它解决了检测不需要的JSON输入的问题。 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)
典型输出:
$ 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:
- February 2018,
go 1.10
introduced a new method json.Decoder.DisallowUnknownFields() which addresses the concern of detecting unwanted JSON-input req.Body
is already anio.Reader
. Reading its entire contents and then performingjson.Unmarshal
wastes resources if the stream was, say a 10MB block of invalid JSON. Parsing the request body, withjson.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)
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)
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论