2 Go语言JSON与XML解析与表单操作

2 Go语言JSON与XML解析与表单操作
- 1 数据交互的格式
- 2 JSON方式
- 2.1 JSON序列化
- 2.2 JSON反序列化
- 2.3 解析到interface
- 3 XML方式
- 3.1 解析XML
- 3.2 生成XML
- 4 字段校验
- 5 文件上传
- 2.1 前后端模拟上传
- 2.2 go客户端模拟上传
- 3 防止重复提交
1 数据交互的格式
常见的数据交互格式有:
- JSON:JavaScript Object Notation,轻量级的数据交换格式,如:
{"name":"lisi","address": ["广州","深圳"]} - XML:工业开发中常用的数据交互标准格式。
2 JSON方式
2.1 JSON序列化
JSON序列化与反序列化需要使用encoding/json包,如下案例所示:
type Person struct {Name stringAge int
}p := Person {Name: "lisi",Age: 50,
}data, _ := json.Marshal(&p)
fmt.Printf(string(data)); //{"Name":"lisi","Age":50}
同理,我们也可以使用上述方法对基本数据类型、切片、map等数据进行序列化。
在结构体序列化时,如果希望序列化后的key的名字可以自定义,可以给该结构体指定一个tag标签:
type Person struct {Name string `json:"my_name"`Age int `json:"my_age"`
}//序列化的结果:{"my_name":"lisi","my_age":50}
在定义struct tag的时候需要注意的几点是:
- 字段的tag是
"-",那么这个字段不会输出到JSON - tag中如果带有
"omitempty"选项,那么如果该字段值为空,就不会输出到JSON串中 - 如果字段类型是bool, string, int, int64等,而tag中带有
",string"选项,那么这个字段在输出到JSON的时候会把该字段对应的值转换成JSON字符串 - JSON对象只支持string作为key,所以要编码一个map,那么必须是map[string]T这种类型(T是Go语言中任意的类型)
- Channel, complex和function是不能被编码成JSON的
- 嵌套的数据是不能编码的,不然会让JSON编码进入死循环
- 指针在编码的时候会输出指针指向的内容,而空指针会输出null
2.2 JSON反序列化
str := `{"Name":"lisi","Age":50}`// 反序列化json为结构体
type Person struct {Name string Age int
}var p Person
json.Unmarshal([]byte(str), &p)
fmt.Println(p) //{lisi 50}
2.3 解析到interface
2.1和2.2的案例中,我们知道json的数据结构,可以直接进行序列化操作,如果不知道JSON具体的结构,就需要解析到interface,因为interface{}可以用来存储任意数据类型的对象。
JSON包中采用map[string]interface{}和[]interface{}结构来存储任意的JSON对象和数组。Go类型和JSON类型的对应关系如下:
- bool 代表 JSON booleans,
- float64 代表 JSON numbers,
- string 代表 JSON strings,
- nil 代表 JSON null
现在我们假设有如下的JSON数据
jsonStr := `{"Name":"Lisi","Age":6,"Parents":["Lisan","WW"]}`
jsonBytes := []byte(jsonStr)var i interface{}
json.Unmarshal(jsonBytes, &i)
fmt.Println(i) // map[Age:6 Name:Lisi Parents:[Lisan WW]]
上述变量i存储了存储了一个map类型,key是strig,值存储在空接口内,
如果在我们不知道他的结构的情况下,我们把他解析到interface{}里面,其真实结构如下:
i = map[string]interface{}{"Name": "Lisi","Age": 6,"Parents": []interface{}{"Lisan","WW",},
}
由于是空接口类型,无法直接访问,需要使用断言方式:
m := i.(map[string]interface{})
for k, v := range m {switch r := v.(type) {case string:fmt.Println(k, " is string ", r)case int:fmt.Println(k, " is int ", r)case []interface{}:fmt.Println(k, " is array ", )for i, u := range r {fmt.Println(i, u)}default:fmt.Println(k, " cannot be recognized")}
}
上面是官方提供的解决方案,操作起来不是很方便,推荐使用第三方包有:
- https://github.com/bitly/go-simplejson
- https://github.com/thedevsaddam/gojsonq
3 XML方式
3.1 解析XML
现在有如下books.xml示例:
<books version="1"><book><bookName>离散数学bookName><bookPrice>120bookPrice>book><book><bookName>人月神话bookName><bookPrice>75bookPrice>book>
books>
通过xml包的Unmarshal函数来解析:
package mainimport ("encoding/xml""fmt""io/ioutil""os"
)type BookStore struct {XMLName xml.Name `xml:"books"`Version string `xml:"version,attr"`Store []book `xml:"book"`Description string `xml:",innerxml"`
}type book struct {XMLName xml.Name `xml:"book"`BookName string `xml:"bookName"`BookPrice string `xml:"bookPrice"`
}func main() {file, err := os.Open("books.xml") if err != nil {fmt.Printf("error: %v", err)return}defer file.Close()data, err := ioutil.ReadAll(file)if err != nil {fmt.Printf("error: %v", err)return}v := BookStore{}err = xml.Unmarshal(data, &v)if err != nil {fmt.Printf("error: %v", err)return}fmt.Println(v)
}
3.2 生成XML
xml包中的Marshal和MarshalIndent两个函数,可以用来生成xml。这两个函数主要的区别是第二个函数会增加前缀和缩进,函数的定义如下所示:
package mainimport ("encoding/xml""fmt""os"
)type BookStore struct {XMLName xml.Name `xml:"books"`Version string `xml:"version,attr"`Store []book `xml:"book"`
}type book struct {BookName string `xml:"bookName"`BookPrice string `xml:"bookPrice"`
}func main() {bs := &BookStore{Version: "1"}bs.Store = append(bs.Store, book{"离散数学", "120"})bs.Store = append(bs.Store, book{"人月神话", "75"})output, err := xml.MarshalIndent(bs, " ", " ")if err != nil {fmt.Printf("error: %v\n", err)}// 生成正确xml头os.Stdout.Write([]byte(xml.Header))os.Stdout.Write(output)
}
4 字段校验
通过内置函数len()可以获取字符串的长度,以此可以校验参数的合法性:
if len(r.Form["username"][0])==0{//为空的处理
}
r.Form对不同类型的表单元素的留空有不同的处理:
- 空文本框、空文本区域以及文件上传,元素的值为空值
- 未选中的复选框和单选按钮,则不会在r.Form中产生相应条目,如果我们用上面例子中的方式去获取数据时程序就会报错。所以我们需要通过
r.Form.Get()来获取值,因为如果字段不存在,通过该方式获取的是空值。但是通过r.Form.Get()只能获取单个的值,
5 文件上传
2.1 前后端模拟上传
前端代码:
<html>
<head><title>上传文件title>
head>
<body>
<form enctype="multipart/form-data" action="/upload" method="post"><input type="file" name="uploadfile" /><input type="submit" value="upload" />
form>
body>
html>
form的enctype属性有如下三种情况:
application/x-www-form-urlencoded # 表示在发送前编码所有字符(默认)
multipart/form-data # 文件上传使用,不会不对字符编码。
text/plain # 空格转换为 "+" 加号,但不对特殊字符编码。
golang的后端处理代码:
// 上传文件处理路由:http.HandleFunc("/upload", upload)
func upload(w http.ResponseWriter, r *http.Request) {// 设置上传文件能使用的内存大小,超过了,则存储在系统临时文件中r.ParseMultipartForm(32 << 20)// 获取上传文件句柄file, handler, err := r.FormFile("uploadfile")if err != nil {fmt.Println(err)return}defer file.Close()fmt.Fprintf(w, "%v", handler.Header)f, err := os.OpenFile("./upload/" + handler.Filename, os.O_WRONLY|os.O_CREATE, 0666)if err != nil {fmt.Println(err)return}defer f.Close()io.Copy(f, file)
}
2.2 go客户端模拟上传
Go支持模拟客户端表单功能支持文件上传:
package mainimport ("bytes""fmt""io""io/ioutil""mime/multipart""net/http""os"
)func postFile(filename string, targetUrl string) error {bodyBuf := &bytes.Buffer{}bodyWriter := multipart.NewWriter(bodyBuf)//关键的一步操作fileWriter, err := bodyWriter.CreateFormFile("uploadfile", filename)if err != nil {fmt.Println("error writing to buffer")return err}//打开文件句柄操作fh, err := os.Open(filename)if err != nil {fmt.Println("error opening file")return err}defer fh.Close()//iocopy_, err = io.Copy(fileWriter, fh)if err != nil {return err}contentType := bodyWriter.FormDataContentType()bodyWriter.Close()resp, err := http.Post(targetUrl, contentType, bodyBuf)if err != nil {return err}defer resp.Body.Close()resp_body, err := ioutil.ReadAll(resp.Body)if err != nil {return err}fmt.Println(resp.Status)fmt.Println(string(resp_body))return nil
}// sample usage
func main() {target_url := "http://localhost:8080/upload"filename := "./test.pdf"postFile(filename, target_url)
}
3 防止重复提交
防止表单重复提交的方案有很多,其中之一是在表单中添加一个带有唯一值的隐藏字段:
- 1.在服务器端生成一个唯一的随机标识号,专业术语称为Token(令牌),同时在当前用户的Session域中保存这个Token。
- 2.将Token发送到客户端的Form表单中,在Form表单中使用隐藏域来存储这个Token
- 3.表单提交的时候连同这个Token一起提交到服务器端,然后在服务器端判断客户端提交上来的Token与服务器端生成的Token是否一致,如果不一致,那就是重复提交了,此时服务器端就可以不处理重复提交的表单。如果相同则处理表单提交,处理完后清除当前用户的Session域中存储的标识号。
在下列情况下,服务器程序将拒绝处理用户提交的表单请求:
- 存储Session域中的Token(令牌)与表单提交的Token(令牌)不同。
- 当前用户的Session中不存在Token(令牌)。
- 用户提交的表单数据中没有Token(令牌)。
用户名:<input type="text" name="username">
密码:<input type="password" name="password">
<input type="hidden" name="token" value="{{.}}">
<input type="submit" value="登陆">
在模版里面增加了一个隐藏字段token,该值通过MD5(时间戳)来确定唯一值,然后我们把这个值存储到服务器端,以方便表单提交时比对判定。
func login(w http.ResponseWriter, r *http.Request) {r.ParseForm()token := r.Form.Get("token")if token != "" {//验证token的合法性} else {//不存在token报错}// 执行具体登录业务
}

本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
