如何在Go(Golang)中以映射(类似于PHP和Ruby)的形式检索表单数据?

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

How to retrieve form-data as map (like PHP and Ruby) in Go (Golang)

问题

我是一名PHP开发者,但目前正在转向Golang...我正在尝试从一个表单(使用Post方法)中检索数据:

<!-- 一个非常简单的表单 -->
<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"]);
?>
// 输出将类似于:
Array
(
    [Name] => Something
    [Email] => Else
    [Message] => For this message
)

但是在Go中...我要么逐个获取数据,要么获取整个数据,但不能像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)

// 输出
map[Name:Something Email:Else Message:For this Message]

请注意,map的键是整个字符串:"Contact[Name]"...

2)遍历整个map r.Form,并使用前缀"Contact["来"解析|获取"那些值,然后将"Contact["和"]"替换为空字符串,这样我就可以像PHP示例那样只获取表单数组的键。

我自己选择了这个解决方法,但是遍历整个表单可能不是一个好主意(?)

// ContactPost处理用户发送的表单
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)))
}
// 输出
map[Name:Something Email:Else Message:For this Message]

这两种解决方案都给我相同的输出...但在第二个示例中,我不一定需要知道"Contact[]"的键。

我知道...我可以忘记那个"表单数组",在我的输入中使用name="Email",然后逐个获取数据,但是...我遇到了一些情况,我使用一个包含多个数据数组的表单,并对每个数组执行不同的操作,比如ORM。

问题1:在Golang中是否有一种更简单的方法来将我的表单数组作为实际的map获取,就像PHP那样?

问题2:我应该逐个获取数据(非常繁琐,而且我可能在某个时候更改表单数据并重新编译...),还是像第二个示例中那样遍历整个表单?

对不起,我的英语不好...提前谢谢!

英文:

I'm a PHP Dev. But currently moving to Golang... I'm trying to retrieve data from a Form (Post method):

&lt;!-- A really SIMPLE form --&gt;
&lt;form class=&quot;&quot; action=&quot;/Contact&quot; method=&quot;post&quot;&gt;
  &lt;input type=&quot;text&quot; name=&quot;Contact[Name]&quot; value=&quot;Something&quot;&gt;   
  &lt;input type=&quot;text&quot; name=&quot;Contact[Email]&quot; value=&quot;Else&quot;&gt;
  &lt;textarea name=&quot;Contact[Message]&quot;&gt;For this message&lt;/textarea&gt;
  &lt;button type=&quot;submit&quot;&gt;Submit&lt;/button&gt;
&lt;/form&gt;

In PHP I would simple use this to get the data:

&lt;?php 
   print_r($_POST[&quot;Contact&quot;])
?&gt;
// Output would be something like this:
Array
(
    [Name] =&gt; Something
    [Email] =&gt; Else
    [Message] =&gt; For this message
)

BUT in go... either I get one by one or the whole thing but not the Contact[] Array only such as PHP

I thought about 2 solutions:

  1. Get one by one:

    // 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]

Note that the map keys are the whole: "Contact[Name]"...

  1. Range whole map r.Form and "parse|obtain" those values with Prefix
    "Contact[" and then replacing "Contact[" and "]" with empty string
    so I can get the Form array Key only such the PHP Example

I went for this work around by my own but... ranging over the whole form may not be a good idea (?)

// 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, &quot;Contact[&quot;) {
		   rp := strings.NewReplacer(&quot;Contact[&quot;, &quot;&quot;, &quot;]&quot;, &quot;&quot;)
		   contact[rp.Replace(i)] = r.Form.Get(i)
	   }
   }

	w.Write([]byte(fmt.Sprint(contact)))
}
//Output
map[Name:Something Email:Else Message:For this Message]

Both solutions give me the same output... But in the 2nd example I don't necessarily need to know the keys of "Contact[]"

I know... I may just forget about that "Form Array" and use name=&quot;Email&quot; on my inputs and retrieve one by one but... I've passing through some scenarios where I use ONE form that contain more than 2 arrays of data and do different things with each one, like ORMs

Question 1: Is there a easier way to get my Form Array as an actual map in Golang like PHP does?

Question 2: Should I retrieve the data one by one (Tedious as much and I may change the Form data at some point and recompile...) or iterate the whole thing as I've done in the 2nd example.

Sorry for my bad English... Thanks in advance!

答案1

得分: 26

有没有更简单的方法在Golang中获取我的表单数组,就像PHP那样将其作为实际的映射?

你可以使用http.Request类型的PostForm成员。它的类型是url.Values,实际上是一个map[string][]string,你可以将其视为这样使用。不过,在使用之前,你仍然需要调用req.ParseForm()

if err := req.ParseForm(); err != nil {
    // 处理错误
}

for key, values := range req.PostForm {
    // [...]
}

**注意,PostForm是一个字符串列表的映射。这是因为理论上,每个字段在POST体中可能出现多次。PostFormValue()方法通过隐式地返回多个值中的第一个值来处理这个问题(也就是说,当你的POST体是&foo=bar&foo=baz时,req.PostFormValue("foo")将始终返回"bar")。

**还要注意,PostForm永远不会包含像你在PHP中使用的嵌套结构。由于Go是静态类型的,POST表单值将始终是string(名称)到[]string(值)的映射。

个人而言,在Go应用程序中,我不会使用方括号语法(contact[email])作为POST字段名称;那是PHP特定的构造,而且正如你已经注意到的,Go对此的支持并不好。

我应该逐个检索数据(非常繁琐,而且我可能会在某个时候更改表单数据并重新编译...),还是像第二个示例中那样遍历整个数据?

对于这个问题可能没有正确的答案。如果你将POST字段映射到具有静态字段的结构体中,你将不得不在某个时候显式地进行映射(或者使用reflect来实现一些神奇的自动映射)。

英文:

>Is there a easier way to get my Form Array as an actual map in Golang like PHP does?

You can use the PostForm member of the http.Request type. It is of type url.Values -- which is actually (ta-da) a map[string][]string, and you can treat is as such. You'll still need to call req.ParseForm() first, though.

if err := req.ParseForm(); err != nil {
    // handle error
}

for key, values := range req.PostForm {
    // [...]
}

Note that PostForm is a map of lists of strings. That's because in theory, each field could be present multiple times in the POST body. The PostFormValue() method handles this by implicitly returning the first of multiple values (meaning, when your POST body is &amp;foo=bar&amp;foo=baz, then req.PostFormValue(&quot;foo&quot;) will always return &quot;bar&quot;).

Also note that PostForm will never contain nested structures like you are used from PHP. As Go is statically typed, a POST form value will always be a mapping of string (name) to []string (value/s).

Personally, I wouldn't use the bracket syntax (contact[email]) for POST field names in Go applications; that's a PHP specific construct, anyway and as you've already noticed, Go does not support it very well.

>Should I retrieve the data one by one (Tedious as much and I may change the Form data at some point and recompile...) or iterate the whole thing as I've done in the 2nd example.

There's probably no correct answer for that. If you are mapping your POST fields to a struct with static fields, you'll have to explicitly map them at some point (or use reflect to implement some magical auto-mapping).

答案2

得分: 13

我遇到了类似的问题,所以我写了这个函数:

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

我可以调用我的函数,并传递类型名为"Contacts":

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

它将返回一个包含两个映射对象的列表,每个映射对象都包含"Name"和"City"键。在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"
            }
        ]
    }
})

如果你的表单数据键结构与我的不完全匹配,那么你可以根据自己的需求调整我使用的正则表达式。

英文:

I had a similar problem, so I wrote this function

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 + &quot;\\[([0-9]+)\\]\\[([a-zA-Z]+)\\]&quot;)
	    matches := re.FindStringSubmatch(key)

	    if len(matches) &gt;= 3 {

		    index, _ := strconv.Atoi(matches[1])

		    for ; index &gt;= len(result); {
			    result = append(result, map[string]string{})
		    }

		    result[index][matches[2]] = values[0]
	    }
    }
    return result
}

It turns a collection of form key value pairs into a list of string maps. For example, if I have form data like this:

Contacts[0][Name] = Alice
Contacts[0][City] = Seattle
Contacts[1][Name] = Bob
Contacts[1][City] = Boston

I can call my function passing the typeName of "Contacts":

for _, contact := range ParseFormCollection(r, &quot;Contacts&quot;) {
    // ...
}

And it will return a list of two map objects, each map containing keys for "Name" and "City". In JSON notation, it would look like this:

[
  {
    &quot;Name&quot;: &quot;Alice&quot;,
    &quot;City&quot;: &quot;Seattle&quot;
  },
  {
    &quot;Name&quot;: &quot;Bob&quot;,
    &quot;City&quot;: &quot;Boston&quot;
  }
]

Which incidentally, is exactly how I'm posting the data up to the server in an ajax request:

$.ajax({
  method: &quot;PUT&quot;,
  url: &quot;/api/example/&quot;,
  dataType: &quot;json&quot;,
  data: {
    Contacts: [
      {
        &quot;Name&quot;: &quot;Alice&quot;,
        &quot;City&quot;: &quot;Seattle&quot;
      },
      {
        &quot;Name&quot;: &quot;Bob&quot;,
        &quot;City&quot;: &quot;Boston&quot;
      }
    ]
  }
})

If your form data key structure doesn't quite match mine, then I you could probably adapt the Regex that I'm using to suit your needs.

答案3

得分: 5

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

我一直在使用点前缀约定: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进行测试:

$ 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的Schema包将表单参数解析为结构体,如下所示:

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进行测试:

$ 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."}}
英文:

I had the same question. The submission of array form params is also idiomatic in the Ruby/Rails world where I'm coming from. But, after some research, it looks like this is not really the "Go-way".

I've been using the dot prefix convention: contact.name, contact.email, etc.

<!-- language: go -->

func parseFormHandler(writer http.ResponseWriter, request *http.Request) {
	request.ParseForm()

	userParams := make(map[string]string)

	for key, _ := range request.Form {
		if strings.HasPrefix(key, &quot;contact.&quot;) {
			userParams[string(key[8:])] = request.Form.Get(key)
		}
	}

	fmt.Fprintf(writer, &quot;%#v\n&quot;, userParams)
}

func main() {
	server := http.Server{Addr: &quot;:8088&quot;}
	http.HandleFunc(&quot;/&quot;, parseFormHandler)
	server.ListenAndServe()
}

Running this server and then curling it:

$ curl -id &quot;contact.name=Jeffrey%20Lebowski&amp;contact.email=thedude@example.com&amp;contact.message=I%20hate%20the%20Eagles,%20man.&quot; http://localhost:8088

Results in:

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{&quot;name&quot;:&quot;Jeffrey Lebowski&quot;, &quot;email&quot;:&quot;thedude@example.com&quot;, &quot;message&quot;:&quot;I hate the Eagles, man.&quot;}

Using the Gorilla Toolkit

You can also use the Gorilla Toolkit's Schema Package to parse the form params into a struct, like so:

<!-- language: go -->

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, &quot;%#v\n&quot;, submission)
}

Running this server and then curling it:

$ curl -id &quot;Contact.Name=Jeffrey%20Lebowski&amp;Contact.Email=thedude@example.com&amp;Contact.Message=I%20hate%20the%20Eagles,%20man.&quot; http://localhost:8088

Results in:

HTTP/1.1 200 OK
Date: Thu, 12 May 2016 17:03:38 GMT
Content-Length: 128
Content-Type: text/plain; charset=utf-8

&amp;main.Submission{Contact:main.Contact{Name:&quot;Jeffrey Lebowski&quot;, Email:&quot;thedude@example.com&quot;, Message:&quot;I hate the Eagles, man.&quot;}}

答案4

得分: 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)
    //然后像这样访问表单值
    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)
    //或者循环遍历Children
    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)
			}
		}
	}
    //如果你想访问文件,请不要忘记调用f.ParseFiles()
}
英文:

> I've been using the dot prefix convention: contact.name, contact.email

I decided to leave a script here so people don't have to spend so much time writing their own custom parser.

Here is a simple script that traverses the form data and puts the values in a struct that follows a format close to PHP and Ruby's.

package formparser

import (
	&quot;strings&quot;
	&quot;mime/multipart&quot;
)

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 := &amp;ValueNode{
		Children: make(map[string]*ValueNode),
	}
	for key, val := range m {
		keys := strings.Split(key,&quot;.&quot;)
		fd.nestValues(n, &amp;keys, val)
	}
	fd.Value = n
}

func (fd *NestedFormData) ParseFiles(m map[string][]*multipart.FileHeader){
	n := &amp;FileNode{
		Children: make(map[string]*FileNode),
	}
	for key, val := range m {
		keys := strings.Split(key,&quot;.&quot;)
		fd.nestFiles(n, &amp;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 := &amp;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 := &amp;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 := &amp;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 := &amp;FileNode{
				Children: make(map[string]*FileNode),
			}
			n.Children[key] = cn
			fd.nestFiles(cn, k,v)
		}
	}
}

Then you can use the package like so:

package main

import (
 &quot;MODULE_PATH/formparser&quot;
 &quot;strconv&quot;
 &quot;fmt&quot;
)

func main(){
    formdata := map[string][]string{
		&quot;contact.name&quot;: []string{&quot;John Doe&quot;},
		&quot;avatars.0.type&quot;: []string{&quot;water&quot;},
        &quot;avatars.0.name&quot;: []string{&quot;Korra&quot;},
		&quot;avatars.1.type&quot;: []string{&quot;air&quot;},
        &quot;avatars.1.name&quot;: []string{&quot;Aang&quot;},
	}
    f := &amp;formparser.NestedFormData{}
    f.ParseValues(formdata)
    //then access form values like so
    fmt.Println(f.Value.Children[&quot;contact&quot;].Children[&quot;name&quot;].Value)
    fmt.Println(f.Value.Children[&quot;avatars&quot;].Children[strconv.Itoa(0)].Children[&quot;name&quot;].Value)
    fmt.Println(f.Value.Children[&quot;avatars&quot;].Children[strconv.Itoa(0)].Children[&quot;type&quot;].Value)
    fmt.Println(f.Value.Children[&quot;avatars&quot;].Children[strconv.Itoa(1)].Children[&quot;name&quot;].Value)
    fmt.Println(f.Value.Children[&quot;avatars&quot;].Children[strconv.Itoa(1)].Children[&quot;type&quot;].Value)
    //or traverse  the Children in a loop
    for key, child := range f.Value.Children {
		fmt.Println(&quot;Key:&quot;, key, &quot;Value:&quot;, child.Value)
		if child.Children != nil {
			for k, c := range child.Children {
				fmt.Println(key + &quot;&#39;s child key:&quot;, k, &quot;Value:&quot;, c.Value)
			}
		}
	}
    //if you want to access files do not forget to call f.ParseFiles()
}

答案5

得分: 0

我写了一些代码,用于将FormData数组转换为JSON字符串。

package phprubyformdatatojson

import (
	"bytes"
	"io"
	"net/url"
	"strconv"
	"strings"
)

type Node struct {
	Name       string
	Value      string
	Subnodes   []*Node
	ArrayValue []*Node
}

func getJsonFromNode(rootNode *Node) string {
	return "{" + nodeToJson(rootNode) + "}"
}

func nodeToJson(n *Node) string {
	if len(n.Subnodes) == 0 && len(n.ArrayValue) == 0 {
		return "\"" + n.Name + "\": \"" + n.Value + "\""
	}

	if len(n.Subnodes) > 0 {
		var parts []string

		for _, subnode := range n.Subnodes {
			parts = append(parts, nodeToJson(subnode))
		}

		if len(n.Name) > 0 {
			return "\"" + n.Name + "\": {" + strings.Join(parts, ", ") + "}"

		} else {
			return strings.Join(parts, ", ")
		}
	}

	if len(n.ArrayValue) > 0 {
		var parts []string
		for _, arrayPart := range n.ArrayValue {
			parts = append(parts, "{"+nodeToJson(arrayPart)+"}")
		}
		return "\"" + n.Name + "\": [" + strings.Join(parts, ", ") + "]"
	}

	return "{}"
}

func addNode(nodeMap map[string]*Node, key string, value string) map[string]*Node {
	keys := splitKeyToParts(key)
	var lastNode *Node
	previosKey := "rootNode"
	totalKey := ""
	for index, keyPart := range keys {
		if totalKey == "" {
			totalKey += keyPart
		} else {
			totalKey += "|||" + keyPart
		}
		isNumber := false
		if _, err := strconv.Atoi(keyPart); err == nil {
			isNumber = true
		}
		if index < len(keys)-1 {
			if z, ok := nodeMap[totalKey]; !ok {
				lastNode = z
				node := &Node{}
				nodeMap[totalKey] = node
				lastNode = node
				prevNode, oook := nodeMap[previosKey]
				if oook {
					if isNumber {
						prevNode.ArrayValue = append(prevNode.ArrayValue, node)
					} else {
						node.Name = keyPart
						prevNode.Subnodes = append(prevNode.Subnodes, node)
					}
				}
			}
		} else {
			lastNode = nodeMap[previosKey]
			newNode := &Node{Name: keyPart, Value: value}
			if isNumber {
				lastNode.ArrayValue = append(lastNode.ArrayValue, newNode)
			} else {
				lastNode.Subnodes = append(lastNode.Subnodes, newNode)
			}
		}
		previosKey = totalKey

	}
	return nodeMap
}

func splitKeyToParts(key string) []string {
	const DELIMITER = "|||||"
	key = strings.Replace(key, "][", DELIMITER, -1)
	key = strings.Replace(key, "[", DELIMITER, -1)
	key = strings.Replace(key, "]", DELIMITER, -1)
	key = strings.Trim(key, DELIMITER)

	return strings.Split(key, DELIMITER)
}

func TransformMapToJsonString(source map[string][]string) string {
	nodesMap := map[string]*Node{}
	nodesMap["rootNode"] = &Node{}

	for key, value := range source {
		nodesMap = addNode(nodesMap, key, strings.Join(value, ""))

	}
	return getJsonFromNode(nodesMap["rootNode"])
}

当你可以手动转换你的请求并使用json.Unmarshal,或者编写gin.Middleware

func PhpRubyArraysToJsonMiddleware(c *gin.Context) {
	body, _ := c.GetRawData()
	m, _ := url.ParseQuery(string(body))
	parsedJson := TransformMapToJsonString(m)
	newBody := []byte(parsedJson)
	c.Request.Body = io.NopCloser(bytes.NewBuffer(newBody))

	c.Next()
}

并像这样使用它:

func handelUpdate(c *gin.Context) {

	req := &YourJsonStruct{}
	if err := c.BindJSON(req); err != nil {
		c.Status(http.StatusBadRequest)
		return
	}

    // 你的代码
}

func main() {
  router := gin.Default()
  router.Use(PhpRubyArraysToJsonMiddleware)
  router.POST("/update", handelUpdate)
}
英文:

I wrote some code, that transforms FormData array to json string.

package phprubyformdatatojson
import (
&quot;bytes&quot;
&quot;io&quot;
&quot;net/url&quot;
&quot;strconv&quot;
&quot;strings&quot;
)
type Node struct {
Name       string
Value      string
Subnodes   []*Node
ArrayValue []*Node
}
func getJsonFromNode(rootNode *Node) string {
return &quot;{&quot; + nodeToJson(rootNode) + &quot;}&quot;
}
func nodeToJson(n *Node) string {
if len(n.Subnodes) == 0 &amp;&amp; len(n.ArrayValue) == 0 {
return &quot;\&quot;&quot; + n.Name + &quot;\&quot;&quot; + &quot;: &quot; + &quot;\&quot;&quot; + n.Value + &quot;\&quot;&quot;
}
if len(n.Subnodes) &gt; 0 {
var parts []string
for _, subnode := range n.Subnodes {
parts = append(parts, nodeToJson(subnode))
}
if len(n.Name) &gt; 0 {
return &quot;\&quot;&quot; + n.Name + &quot;\&quot;&quot; + &quot;: {&quot; + strings.Join(parts, &quot;, &quot;) + &quot;}&quot;
} else {
return strings.Join(parts, &quot;, &quot;)
}
}
if len(n.ArrayValue) &gt; 0 {
var parts []string
for _, arrayPart := range n.ArrayValue {
parts = append(parts, &quot;{&quot;+nodeToJson(arrayPart)+&quot;}&quot;)
}
return &quot;\&quot;&quot; + n.Name + &quot;\&quot;&quot; + &quot;: [&quot; + strings.Join(parts, &quot;, &quot;) + &quot;]&quot;
}
return &quot;{}&quot;
}
func addNode(nodeMap map[string]*Node, key string, value string) map[string]*Node {
keys := splitKeyToParts(key)
var lastNode *Node
previosKey := &quot;rootNode&quot;
totalKey := &quot;&quot;
for index, keyPart := range keys {
if totalKey == &quot;&quot; {
totalKey += keyPart
} else {
totalKey += &quot;|||&quot; + keyPart
}
isNumber := false
if _, err := strconv.Atoi(keyPart); err == nil {
isNumber = true
}
if index &lt; len(keys)-1 {
if z, ok := nodeMap[totalKey]; !ok {
lastNode = z
node := &amp;Node{}
nodeMap[totalKey] = node
lastNode = node
prevNode, oook := nodeMap[previosKey]
if oook {
if isNumber {
prevNode.ArrayValue = append(prevNode.ArrayValue, node)
} else {
node.Name = keyPart
prevNode.Subnodes = append(prevNode.Subnodes, node)
}
}
}
} else {
lastNode = nodeMap[previosKey]
newNode := &amp;Node{Name: keyPart, Value: value}
if isNumber {
lastNode.ArrayValue = append(lastNode.ArrayValue, newNode)
} else {
lastNode.Subnodes = append(lastNode.Subnodes, newNode)
}
}
previosKey = totalKey
}
return nodeMap
}
func splitKeyToParts(key string) []string {
const DELIMITER = &quot;|||||&quot;
key = strings.Replace(key, &quot;][&quot;, DELIMITER, -1)
key = strings.Replace(key, &quot;[&quot;, DELIMITER, -1)
key = strings.Replace(key, &quot;]&quot;, DELIMITER, -1)
key = strings.Trim(key, DELIMITER)
return strings.Split(key, DELIMITER)
}
func TransformMapToJsonString(source map[string][]string) string {
nodesMap := map[string]*Node{}
nodesMap[&quot;rootNode&quot;] = &amp;Node{}
for key, value := range source {
nodesMap = addNode(nodesMap, key, strings.Join(value, &quot;&quot;))
}
return getJsonFromNode(nodesMap[&quot;rootNode&quot;])
}

When you can manualy transform you request and json.Unmarshal it, or write gin.Middleware

func PhpRubyArraysToJsonMiddleware(c *gin.Context) {
body, _ := c.GetRawData()
m, _ := url.ParseQuery(string(body))
parsedJson := TransformMapToJsonString(m)
newBody := []byte(parsedJson)
c.Request.Body = io.NopCloser(bytes.NewBuffer(newBody))
c.Next()
}

and use it like this

func handelUpdate(c *gin.Context) {
req := &amp;YourJsonStruct{}
if err := c.BindJSON(req); err != nil {
c.Status(http.StatusBadRequest)
return
}
// your code
}
func main() {
router := gin.Default()
router.Use(PhpRubyArraysToJsonMiddleware)
router.POST(&quot;/update&quot;, handelUpdate)
}
</details>

huangapple
  • 本文由 发表于 2016年1月17日 23:04:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/34839811.html
匿名

发表评论

匿名网友

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

确定