如何检索表单数据(作为数组)

时间:2016-01-17 15:04:59

标签: html arrays forms go

我是PHP Dev。但目前转向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
)

但是去...要么我一个接一个,要么全部接受,而不是像PHP这样的Contact []数组

我想到了两个解决方案:

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]

请注意,地图键是整体:&#34;联系[姓名]&#34; ...

2)范围整个地图r.Form和&#34;解析|获得&#34;带有前缀的那些值 &#34;联系[&#34;然后更换&#34;联系[&#34;和&#34;]&#34;用空字符串 所以我只能获得Form数组Key这样的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]

两种解决方案都给我相同的输出......但在第二个例子中,我不一定需要知道&#34;联系[]&#34;

的键。

我知道......我可能会忘记那个&#34; Form Array&#34;并在我的输入上使用name="Email"并逐个检索但是...我已经通过一些场景,我使用一个包含2个以上数据阵列的表单,并对每个表单执行不同的操作,例如奥姆斯

问题1 :有没有更简单的方法可以像在PHP一样将Go Form作为Golang中的实际地图?

问题2 :我应该逐个检索数据(单调乏味,我可能会在某个时候更改表单数据并重新编译......)或者重复整个事情,因为我&#39 ;在第二个例子中完成了。

抱歉我的英语不好......提前致谢!

4 个答案:

答案 0 :(得分:16)

  

有没有一种更简单的方法可以将我的Form Array作为Golang中的实际地图,就像PHP一样?

您可以使用PostForm类型的http.Request成员。它的类型为url.Values - 实际上是(ta-da)a 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")将始终返回{{1} }})。

另请注意,"bar"永远不会包含嵌套结构 ,就像您在PHP中使用的一样。由于Go是静态类型,因此POST表单值始终PostForm(名称)到string(值/ s)的映射。

就个人而言,我不会在Go应用程序中对POST字段名称使用括号语法([]string);这是一个PHP特定的构造,无论如何,正如你已经注意到的那样,Go并不支持它。

  

我应该逐个检索数据(单调乏味,我可能会在某个时候更改表格数据并重新编译......)或者按照我在第二个例子中所做的那样迭代整个数据。

可能没有正确答案。如果要将POST字段映射到具有静态字段的结构,则必须在某个时刻显式映射它们(或使用reflect来实现一些神奇的自动映射)。

答案 1 :(得分:2)

我有同样的问题。数组形式参数的提交在我来自的Ruby / Rails世界中也是惯用的。但是,经过一些研究,看起来这不是真正的“走向”。

我一直在使用点前缀约定:contact.namecontact.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.Server{Addr: ":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

您还可以使用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.Submission{Contact:main.Contact{Name:"Jeffrey Lebowski", Email:"thedude@example.com", Message:"I hate the Eagles, man."}}

答案 2 :(得分: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

我可以调用我的函数传递&#34; Contacts&#34;:

的typeName
for _, contact := range ParseFormCollection(r, "Contacts") {
    // ...
}

它将返回一个包含两个地图对象的列表,每个地图包含&#34; Name&#34;和&#34;城市&#34;。在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 :(得分:0)

我一直在使用点前缀约定: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()
}