如何在 Go (Golang) 中检索表单数据作为地图(如 PHP 和 Ruby)

     2023-02-22     197

关键词:

【中文标题】如何在 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.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.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 正文为 &amp;foo=bar&amp;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(); ...让它工作!我不知道为什么...谢谢

golang在go(golang)中进行分段上传的示例,客户端创建http请求而不是html表单。(代码片段)

查看详情

带有 mgo 的 Go (golang) 中的 MongoDB:如何更新记录、确定更新是不是成功并在单个原子操作中获取数据?

】带有mgo的Go(golang)中的MongoDB:如何更新记录、确定更新是不是成功并在单个原子操作中获取数据?【英文标题】:MongoDBinGo(golang)withmgo:HowdoIupdatearecord,findoutifupdatewassuccessfulandgetthedatainasingleatomicoperation?带有mgo的Go(golang)中的MongoDB... 查看详情

如何使用复选框检索数据库中的特定数据

】如何使用复选框检索数据库中的特定数据【英文标题】:Howtousecheckboxestoretrievespecificdatainadatabase【发布时间】:2014-10-2102:04:40【问题描述】:最近我一直在开发一个表单,用户可以使用该表单选择一个复选框,并且作为选择的... 查看详情

go使用graphql-基础教程

参考技术A欢迎golang同胞!在本教程中,我们将研究如何在基于Go的程序中与GraphQL服务器进行交互。在本教程结束时,我们应该知道如何执行以下操作:在本教程中,我们将专注于学习GraphQL的数据检索方面,并且我们将使用内存... 查看详情

如何在后续瀑布步骤中检索自适应卡的表单提交

】如何在后续瀑布步骤中检索自适应卡的表单提交【英文标题】:HowtoretrieveAdaptiveCard\'sformsubmissioninsubsequentwaterfallstep【发布时间】:2019-07-3008:59:25【问题描述】:我正在使用BotFramework(V4),我有一个包含两个步骤的WaterfallDialog;... 查看详情

如何检索文本字段文本并将其作为整数 32 存储在核心数据中?

】如何检索文本字段文本并将其作为整数32存储在核心数据中?【英文标题】:HowcanIretrieveatextfieldstextandstoreitasaninteger32incoredata?【发布时间】:2012-05-0300:22:49【问题描述】:我正在尝试获取文本字段文本,将其转换为int,并将其... 查看详情

go_15:golang中面向对象的三大特性

  有过JAVA语言学习经历的朋友都知道,面向对象主要包括了三个基本特征:封装、继承和多态。封装,就是指运行的数据和函数绑定在一起,JAVA中主要是通过super指针来完成的;继承,就是指class之间可以相互继承属性和函数... 查看详情

如何使用 Java、AJAX 使用 Rest Web 服务从 MySQL 数据库中检索数据并放置在 HTML 表单中

】如何使用Java、AJAX使用RestWeb服务从MySQL数据库中检索数据并放置在HTML表单中【英文标题】:HowtoretrievedatafromMySQLdatabaseandplaceinaHTMLFormusingRestWebServicesusingJava,AJAX【发布时间】:2018-09-0501:37:40【问题描述】:我正在尝试从MySQL数据... 查看详情

如何通过在 Google 数据存储中传递 ID 数组作为输入来检索实体?

】如何通过在Google数据存储中传递ID数组作为输入来检索实体?【英文标题】:HowtoretrieveentitiesbypassingarrayofIDsasinputinGoogledatastore?【发布时间】:2021-04-2502:31:13【问题描述】:我正在尝试在数据存储区中实现以下SQL逻辑,SELECT*from... 查看详情

如何在jquery中获取表单数据作为对象[重复]

】如何在jquery中获取表单数据作为对象[重复]【英文标题】:Howtogetformdataasaobjectinjquery[duplicate]【发布时间】:2011-01-2501:57:01【问题描述】:我试过jQuery(\'#form_id\').serialize()。这仅将表单数据作为url编码字符串返回。是否可以将表... 查看详情

在 Go (Golang) 中查找文件系统对象

】在Go(Golang)中查找文件系统对象【英文标题】:FindfilesystemobjectsinGo(Golang)【发布时间】:2021-12-0815:33:08【问题描述】:我正在尝试使用Go查找匹配的文件系统对象并确定我作为输入接收到的路径类型。具体来说,如果对象与提供... 查看详情

如何在golang go-face中捕获每张脸

】如何在golanggo-face中捕获每张脸【英文标题】:HowtoCaptureeachfaceingolanggo-face【发布时间】:2021-07-0821:12:42【问题描述】:我在GoLang中使用https://github.com/Kagami/go-face进行人脸识别,我尝试了go-face库中给出的示例。在该示例中,它... 查看详情

如何从 Firebase 检索图像作为布尔数据?

】如何从Firebase检索图像作为布尔数据?【英文标题】:HowRetrieveImagefromFirebaseasBooleandata?【发布时间】:2017-01-1302:08:47【问题描述】:我想知道如何从Firebase存储中检索图像作为布尔数据。我知道我必须有一个占位符才能显示图... 查看详情

如何在 useEffect 挂钩中形成 setFieldValue

】如何在useEffect挂钩中形成setFieldValue【英文标题】:HowtoFormiksetFieldValueinuseEffecthook【发布时间】:2020-06-0701:47:51【问题描述】:我有一个Formik表单,它需要根据通过路由器传递的信息动态更改。我需要运行graphQL查询来检索一些... 查看详情

如何在 golang 的同一个处理程序中处理多个 POST 请求?

】如何在golang的同一个处理程序中处理多个POST请求?【英文标题】:HowtohandlemultiplePOSTrequestsinsamehandleringolang?【发布时间】:2022-01-2101:29:16【问题描述】:我要执行的signup.html文件中有两个表单。第一个表单重定向到/login,但不... 查看详情

如何在 prometheus/client_golang 中禁用 go_collector 指标

】如何在prometheus/client_golang中禁用go_collector指标【英文标题】:Howtodisablego_collectormetricsinprometheus/client_golang【发布时间】:2016-05-0903:56:12【问题描述】:我正在使用NewGaugeVec报告我的指标:elapsed:=prometheus.NewGaugeVec(prometheus.GaugeOpts... 查看详情

golang数组

数组是Go语言编程中最常用的数据结构之一。顾名思义,数组就是指一系列同一类型数据的集合。数组中包含的每个数据被称为数组元素(element),一个数组包含的元素个数被称为数组的长度。在Go语言中数组是一个值类型(valu... 查看详情

如何将 html 存储在 mysql 数据库中并将其作为 html 在 nodejs 应用程序中检索?

】如何将html存储在mysql数据库中并将其作为html在nodejs应用程序中检索?【英文标题】:Howtostorehtmlinmysqldatabaseandretrieveitashtmlinnodejsapp?【发布时间】:2021-08-0509:21:06【问题描述】:假设我存储&lt;b&gt;boldtext&lt;/b&gt;在mysq... 查看详情