如何在 Go (Golang) 中检索表单数据作为地图(如 PHP 和 Ruby)
Posted
技术标签:
【中文标题】如何在 Go (Golang) 中检索表单数据作为地图(如 PHP 和 Ruby)【英文标题】:How to retrieve form-data as map (like PHP and Ruby) in Go (Golang) 【发布时间】:2016-04-22 17:50:44 【问题描述】:我是一名 php 开发人员。但目前正在迁移到 Golang...我正在尝试从 Form(Post 方法)中检索数据:
<!-- A really SIMPLE form -->
<form class="" action="/Contact" method="post">
<input type="text" name="Contact[Name]" value="Something">
<input type="text" name="Contact[Email]" value="Else">
<textarea name="Contact[Message]">For this message</textarea>
<button type="submit">Submit</button>
</form>
在 PHP 中,我会简单地使用它来获取数据:
<?php
print_r($_POST["Contact"])
?>
// Output would be something like this:
Array
(
[Name] => Something
[Email] => Else
[Message] => For this message
)
但是在进行中...要么我一个接一个地得到,要么得到整个东西,但不是 Contact[] 数组,例如 PHP
我想到了 2 个解决方案:
1)一一获取:
// r := *http.Request
err := r.ParseForm()
if err != nil
w.Write([]byte(err.Error()))
return
contact := make(map[string]string)
contact["Name"] = r.PostFormValue("Contact[Name]")
contact["Email"] = r.PostFormValue("Contact[Email]")
contact["Message"] = r.PostFormValue("Contact[Message]")
fmt.Println(contact)
// Output
map[Name:Something Email:Else Message:For this Message]
请注意,地图键是完整的:“Contact[Name]”...
2) 范围整个地图r.Form
并“解析|获取”带有前缀的那些值
"Contact[" 然后用空字符串替换 "Contact[" 和 "]"
所以我只能通过 PHP 示例获取表单数组键
我自己完成了这项工作,但是...覆盖整个表格可能不是一个好主意 (?)
// ContactPost process the form sent by the user
func ContactPost(w http.ResponseWriter, r *http.Request, ps httprouter.Params)
err := r.ParseForm()
if err != nil
w.Write([]byte(err.Error()))
return
contact := make(map[string]string)
for i := range r.Form
if strings.HasPrefix(i, "Contact[")
rp := strings.NewReplacer("Contact[", "", "]", "")
contact[rp.Replace(i)] = r.Form.Get(i)
w.Write([]byte(fmt.Sprint(contact)))
//Output
map[Name:Something Email:Else Message:For this Message]
两种解决方案都给我相同的输出...但在第二个示例中,我不一定需要知道“Contact[]”的键
我知道...我可能会忘记那个“表单数组”并在我的输入上使用name="Email"
并一一检索但是...我已经经历了一些场景,我使用一个包含更多的表单超过 2 个数据数组,并且对每个数组做不同的事情,比如 ORMs
问题 1:有没有更简单的方法让我的表单数组像 PHP 一样在 Golang 中作为实际地图?
问题 2:我应该逐一检索数据(同样繁琐,我可能会在某些时候更改表单数据并重新编译...)还是像我一样迭代整个事情在第二个例子中完成。
抱歉我的英语不好...提前谢谢!
【问题讨论】:
***.com/questions/23713817/… 不,这无济于事...请注意我的表单数据数组具有独立的键,即使没有键也无法正确识别哪个对应于哪个... 您可以很容易地编写自己的表单解析器,它遍历表单中的所有键,并将所有“Name[Key]”字段分组到一个子字典中。 因为我找不到类似的东西,这几乎就是我所做的,我自己的功能......好吧,我会为此创建一个结构来保存解析的表单并根据需要使用它 【参考方案1】:我一直在使用点前缀约定:contact.name、contact.email
我决定在这里留下一个脚本,这样人们就不必花太多时间编写自己的自定义解析器。
这是一个简单的脚本,它遍历表单数据并将值放入遵循 PHP 和 Ruby 格式的结构中。
package formparser
import (
"strings"
"mime/multipart"
)
type NestedFormData struct
Value *ValueNode
File *FileNode
type ValueNode struct
Value []string
Children map[string]*ValueNode
type FileNode struct
Value []*multipart.FileHeader
Children map[string]*FileNode
func (fd *NestedFormData) ParseValues(m map[string][]string)
n := &ValueNode
Children: make(map[string]*ValueNode),
for key, val := range m
keys := strings.Split(key,".")
fd.nestValues(n, &keys, val)
fd.Value = n
func (fd *NestedFormData) ParseFiles(m map[string][]*multipart.FileHeader)
n := &FileNode
Children: make(map[string]*FileNode),
for key, val := range m
keys := strings.Split(key,".")
fd.nestFiles(n, &keys, val)
fd.File = n
func (fd *NestedFormData) nestValues(n *ValueNode, k *[]string, v []string)
var key string
key, *k = (*k)[0], (*k)[1:]
if len(*k) == 0
if _, ok := n.Children[key]; ok
n.Children[key].Value = append(n.Children[key].Value, v...)
else
cn := &ValueNode
Value: v,
Children: make(map[string]*ValueNode),
n.Children[key] = cn
else
if _, ok := n.Children[key]; ok
fd.nestValues(n.Children[key], k,v)
else
cn := &ValueNode
Children: make(map[string]*ValueNode),
n.Children[key] = cn
fd.nestValues(cn, k,v)
func (fd *NestedFormData) nestFiles(n *FileNode, k *[]string, v []*multipart.FileHeader)
var key string
key, *k = (*k)[0], (*k)[1:]
if len(*k) == 0
if _, ok := n.Children[key]; ok
n.Children[key].Value = append(n.Children[key].Value, v...)
else
cn := &FileNode
Value: v,
Children: make(map[string]*FileNode),
n.Children[key] = cn
else
if _, ok := n.Children[key]; ok
fd.nestFiles(n.Children[key], k,v)
else
cn := &FileNode
Children: make(map[string]*FileNode),
n.Children[key] = cn
fd.nestFiles(cn, k,v)
然后你可以像这样使用这个包:
package main
import (
"MODULE_PATH/formparser"
"strconv"
"fmt"
)
func main()
formdata := map[string][]string
"contact.name": []string"John Doe",
"avatars.0.type": []string"water",
"avatars.0.name": []string"Korra",
"avatars.1.type": []string"air",
"avatars.1.name": []string"Aang",
f := &formparser.NestedFormData
f.ParseValues(formdata)
//then access form values like so
fmt.Println(f.Value.Children["contact"].Children["name"].Value)
fmt.Println(f.Value.Children["avatars"].Children[strconv.Itoa(0)].Children["name"].Value)
fmt.Println(f.Value.Children["avatars"].Children[strconv.Itoa(0)].Children["type"].Value)
fmt.Println(f.Value.Children["avatars"].Children[strconv.Itoa(1)].Children["name"].Value)
fmt.Println(f.Value.Children["avatars"].Children[strconv.Itoa(1)].Children["type"].Value)
//or traverse the Children in a loop
for key, child := range f.Value.Children
fmt.Println("Key:", key, "Value:", child.Value)
if child.Children != nil
for k, c := range child.Children
fmt.Println(key + "'s child key:", k, "Value:", c.Value)
//if you want to access files do not forget to call f.ParseFiles()
【讨论】:
【参考方案2】:我也遇到了类似的问题,所以写了这个函数
func ParseFormCollection(r *http.Request, typeName string) []map[string]string
var result []map[string]string
r.ParseForm()
for key, values := range r.Form
re := regexp.MustCompile(typeName + "\\[([0-9]+)\\]\\[([a-zA-Z]+)\\]")
matches := re.FindStringSubmatch(key)
if len(matches) >= 3
index, _ := strconv.Atoi(matches[1])
for ; index >= len(result);
result = append(result, map[string]string)
result[index][matches[2]] = values[0]
return result
它将表单键值对的集合转换为字符串映射列表。例如,如果我有这样的表单数据:
Contacts[0][Name] = Alice
Contacts[0][City] = Seattle
Contacts[1][Name] = Bob
Contacts[1][City] = Boston
我可以通过“联系人”的 typeName 调用我的函数:
for _, contact := range ParseFormCollection(r, "Contacts")
// ...
它会返回一个包含两个地图对象的列表,每个地图包含“名称”和“城市”的键。在 JSON 表示法中,它看起来像这样:
[
"Name": "Alice",
"City": "Seattle"
,
"Name": "Bob",
"City": "Boston"
]
顺便说一句,这正是我在 ajax 请求中将数据发布到服务器的方式:
$.ajax(
method: "PUT",
url: "/api/example/",
dataType: "json",
data:
Contacts: [
"Name": "Alice",
"City": "Seattle"
,
"Name": "Bob",
"City": "Boston"
]
)
如果您的表单数据键结构与我的不太匹配,那么您可以调整我正在使用的正则表达式以满足您的需求。
【讨论】:
很好的答案。我发现直接将结果附加到地图(丢弃索引)更加灵活。这样,如果索引不连续就没有问题。【参考方案3】:我也有同样的问题。数组表单参数的提交在我来自的 Ruby/Rails 世界中也是惯用的。但是,经过一些研究,看起来这并不是真正的“Go-way”。
我一直在使用点前缀约定:contact.name
、contact.email
等。
func parseFormHandler(writer http.ResponseWriter, request *http.Request)
request.ParseForm()
userParams := make(map[string]string)
for key, _ := range request.Form
if strings.HasPrefix(key, "contact.")
userParams[string(key[8:])] = request.Form.Get(key)
fmt.Fprintf(writer, "%#v\n", userParams)
func main()
server := http.ServerAddr: ":8088"
http.HandleFunc("/", parseFormHandler)
server.ListenAndServe()
运行这个服务器然后卷曲它:
$ curl -id "contact.name=Jeffrey%20Lebowski&contact.email=thedude@example.com&contact.message=I%20hate%20the%20Eagles,%20man." http://localhost:8088
结果:
HTTP/1.1 200 OK
Date: Thu, 12 May 2016 16:41:44 GMT
Content-Length: 113
Content-Type: text/plain; charset=utf-8
map[string]string"name":"Jeffrey Lebowski", "email":"thedude@example.com", "message":"I hate the Eagles, man."
使用大猩猩工具包
您还可以使用Gorilla Toolkit's Schema Package 将表单参数解析为结构,如下所示:
type Submission struct
Contact Contact
type Contact struct
Name string
Email string
Message string
func parseFormHandler(writer http.ResponseWriter, request *http.Request)
request.ParseForm()
decoder := schema.NewDecoder()
submission := new(Submission)
err := decoder.Decode(submission, request.Form)
if err != nil
log.Fatal(err)
fmt.Fprintf(writer, "%#v\n", submission)
运行这个服务器然后卷曲它:
$ curl -id "Contact.Name=Jeffrey%20Lebowski&Contact.Email=thedude@example.com&Contact.Message=I%20hate%20the%20Eagles,%20man." http://localhost:8088
结果:
HTTP/1.1 200 OK
Date: Thu, 12 May 2016 17:03:38 GMT
Content-Length: 128
Content-Type: text/plain; charset=utf-8
&main.SubmissionContact:main.ContactName:"Jeffrey Lebowski", Email:"thedude@example.com", Message:"I hate the Eagles, man."
【讨论】:
【参考方案4】:有没有更简单的方法让我的表单数组像 PHP 一样在 Golang 中作为实际地图?
您可以使用http.Request
类型的PostForm
成员。它是url.Values
类型——实际上是(ta-da)map[string][]string
,你可以这样对待。不过,您仍然需要先致电req.ParseForm()
。
if err := req.ParseForm(); err != nil
// handle error
for key, values := range req.PostForm
// [...]
请注意,PostForm
是字符串列表的映射。这是因为理论上,每个字段都可以在 POST 正文中出现多次。 PostFormValue()
方法通过隐式返回多个值的 first 来处理此问题(这意味着,当您的 POST 正文为 &foo=bar&foo=baz
时,req.PostFormValue("foo")
将始终返回 "bar"
)。
另请注意,PostForm
绝不会像 PHP 中使用的那样包含 嵌套结构。由于 Go 是静态类型的,POST 表单值总是是string
(名称)到[]string
(值/秒)的映射。
就我个人而言,我不会对 Go 应用程序中的 POST 字段名称使用括号语法 (contact[email]
);无论如何,这是一个特定于 PHP 的构造,正如您已经注意到的那样,Go 并不能很好地支持它。
我应该一一检索数据(同样繁琐,我可能会在某个时候更改表单数据并重新编译...)还是像我在第二个示例中所做的那样迭代整个事情。
对此可能没有正确的答案。如果您将 POST 字段映射到具有静态字段的结构,则必须在某些时候显式映射它们(或使用 reflect
来实现一些神奇的自动映射)。
【讨论】:
我必须在你的代码之前添加 req.PostFormValue("") if err := req.ParseForm(); ...让它工作!我不知道为什么...谢谢以上是关于如何在 Go (Golang) 中检索表单数据作为地图(如 PHP 和 Ruby)的主要内容,如果未能解决你的问题,请参考以下文章
如何将从表单提交中检索到的参数作为参数传递给 django 中的函数
golang-101-hacks(12)——切片作为函数参数传递
带有 mgo 的 Go (golang) 中的 MongoDB:如何更新记录、确定更新是不是成功并在单个原子操作中获取数据?