Files
2017-06-10 11:58:54 +08:00

244 lines
8.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 7.2 JSON处理
JSONJavascript Object Notation是一种轻量级的数据交换语言以文字为基础具有自我描述性且易于让人阅读。尽管JSON是Javascript的一个子集但JSON是独立于语言的文本格式并且采用了类似于C语言家族的一些习惯。JSON与XML最大的不同在于XML是一个完整的标记语言而JSON不是。JSON由于比XML更小、更快更易解析,以及浏览器的内建快速解析支持,使得其更适用于网络数据传输领域。目前我们看到很多的开放平台基本上都是采用了JSON作为他们的数据交互的接口。既然JSON在Web开发中如此重要那么Go语言对JSON支持的怎么样呢Go语言的标准库已经非常好的支持了JSON可以很容易的对JSON数据进行编、解码的工作。
前一小节的运维的例子用json来表示结果描述如下
```json
{"servers":[{"serverName":"Shanghai_VPN","serverIP":"127.0.0.1"},{"serverName":"Beijing_VPN","serverIP":"127.0.0.2"}]}
```
本小节余下的内容将以此JSON数据为基础来介绍go语言的json包对JSON数据的编、解码。
## 解析JSON
### 解析到结构体
假如有了上面的JSON串那么我们如何来解析这个JSON串呢Go的JSON包中有如下函数
```Go
func Unmarshal(data []byte, v interface{}) error
```
通过这个函数我们就可以实现解析的目的,详细的解析例子请看如下代码:
```Go
package main
import (
"encoding/json"
"fmt"
)
type Server struct {
ServerName string
ServerIP string
}
type Serverslice struct {
Servers []Server
}
func main() {
var s Serverslice
str := `{"servers":[{"serverName":"Shanghai_VPN","serverIP":"127.0.0.1"},{"serverName":"Beijing_VPN","serverIP":"127.0.0.2"}]}`
json.Unmarshal([]byte(str), &s)
fmt.Println(s)
}
```
在上面的示例代码中我们首先定义了与json数据对应的结构体数组对应slice字段名对应JSON里面的KEY在解析的时候如何将json数据与struct字段相匹配呢例如JSON的key是`Foo`,那么怎么找对应的字段呢?
- 首先查找tag含有`Foo`的可导出的struct字段(首字母大写)
- 其次查找字段名是`Foo`的导出字段
- 最后查找类似`FOO`或者`FoO`这样的除了首字母之外其他大小写不敏感的导出字段
聪明的你一定注意到了这一点:能够被赋值的字段必须是可导出字段(即首字母大写。同时JSON解析的时候只会解析能找得到的字段找不到的字段会被忽略这样的一个好处是当你接收到一个很大的JSON数据结构而你却只想获取其中的部分数据的时候你只需将你想要的数据对应的字段名大写即可轻松解决这个问题。
### 解析到interface
上面那种解析方式是在我们知晓被解析的JSON数据的结构的前提下采取的方案如果我们不知道被解析的数据的格式又应该如何来解析呢
我们知道interface{}可以用来存储任意数据类型的对象这种数据结构正好用于存储解析的未知结构的json数据的结果。JSON包中采用map[string]interface{}和[]interface{}结构来存储任意的JSON对象和数组。Go类型和JSON类型的对应关系如下
- bool 代表 JSON booleans,
- float64 代表 JSON numbers,
- string 代表 JSON strings,
- nil 代表 JSON null.
现在我们假设有如下的JSON数据
```Go
b := []byte(`{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}`)
```
如果在我们不知道他的结构的情况下我们把他解析到interface{}里面
```Go
var f interface{}
err := json.Unmarshal(b, &f)
```
这个时候f里面存储了一个map类型他们的key是string值存储在空的interface{}里
```Go
f = map[string]interface{}{
"Name": "Wednesday",
"Age": 6,
"Parents": []interface{}{
"Gomez",
"Morticia",
},
}
```
那么如何来访问这些数据呢?通过断言的方式:
```Go
m := f.(map[string]interface{})
```
通过断言之后,你就可以通过如下方式来访问里面的数据了
```Go
for k, v := range m {
switch vv := v.(type) {
case string:
fmt.Println(k, "is string", vv)
case int:
fmt.Println(k, "is int", vv)
case float64:
fmt.Println(k,"is float64",vv)
case []interface{}:
fmt.Println(k, "is an array:")
for i, u := range vv {
fmt.Println(i, u)
}
default:
fmt.Println(k, "is of a type I don't know how to handle")
}
}
```
通过上面的示例可以看到通过interface{}与type assert的配合我们就可以解析未知结构的JSON数了。
上面这个是官方提供的解决方案其实很多时候我们通过类型断言操作起来不是很方便目前bitly公司开源了一个叫做`simplejson`的包,在处理未知结构体的JSON时相当方便详细例子如下所示
```Go
js, err := NewJson([]byte(`{
"test": {
"array": [1, "2", 3],
"int": 10,
"float": 5.150,
"bignum": 9223372036854775807,
"string": "simplejson",
"bool": true
}
}`))
arr, _ := js.Get("test").Get("array").Array()
i, _ := js.Get("test").Get("int").Int()
ms := js.Get("test").Get("string").MustString()
```
可以看到使用这个库操作JSON比起官方包来说简单的多,详细的请参考如下地址https://github.com/bitly/go-simplejson
## 生成JSON
我们开发很多应用的时候最后都是要输出JSON数据串那么如何来处理呢JSON包里面通过`Marshal`函数来处理,函数定义如下:
```Go
func Marshal(v interface{}) ([]byte, error)
```
假设我们还是需要生成上面的服务器列表信息,那么如何来处理呢?请看下面的例子:
```Go
package main
import (
"encoding/json"
"fmt"
)
type Server struct {
ServerName string
ServerIP string
}
type Serverslice struct {
Servers []Server
}
func main() {
var s Serverslice
s.Servers = append(s.Servers, Server{ServerName: "Shanghai_VPN", ServerIP: "127.0.0.1"})
s.Servers = append(s.Servers, Server{ServerName: "Beijing_VPN", ServerIP: "127.0.0.2"})
b, err := json.Marshal(s)
if err != nil {
fmt.Println("json err:", err)
}
fmt.Println(string(b))
}
```
输出如下内容:
```json
{"Servers":[{"ServerName":"Shanghai_VPN","ServerIP":"127.0.0.1"},{"ServerName":"Beijing_VPN","ServerIP":"127.0.0.2"}]}
```
我们看到上面的输出字段名的首字母都是大写的如果你想用小写的首字母怎么办呢把结构体的字段名改成首字母小写的JSON输出的时候必须注意只有导出的字段才会被输出如果修改字段名那么就会发现什么都不会输出所以必须通过struct tag定义来实现
```Go
type Server struct {
ServerName string `json:"serverName"`
ServerIP string `json:"serverIP"`
}
type Serverslice struct {
Servers []Server `json:"servers"`
}
```
通过修改上面的结构体定义输出的JSON串就和我们最开始定义的JSON串保持一致了。
针对JSON的输出我们在定义struct tag的时候需要注意的几点是:
- 字段的tag是`"-"`那么这个字段不会输出到JSON
- tag中带有自定义名称那么这个自定义名称会出现在JSON的字段名中例如上面例子中serverName
- tag中如果带有`"omitempty"`选项那么如果该字段值为空就不会输出到JSON串中
- 如果字段类型是bool, string, int, int64等而tag中带有`",string"`选项那么这个字段在输出到JSON的时候会把该字段对应的值转换成JSON字符串
举例来说:
```Go
type Server struct {
// ID 不会导出到JSON中
ID int `json:"-"`
// ServerName2 的值会进行二次JSON编码
ServerName string `json:"serverName"`
ServerName2 string `json:"serverName2,string"`
// 如果 ServerIP 为空则不输出到JSON串中
ServerIP string `json:"serverIP,omitempty"`
}
s := Server {
ID: 3,
ServerName: `Go "1.0" `,
ServerName2: `Go "1.0" `,
ServerIP: ``,
}
b, _ := json.Marshal(s)
os.Stdout.Write(b)
```
会输出以下内容:
```json
{"serverName":"Go \"1.0\" ","serverName2":"\"Go \\\"1.0\\\" \""}
```
Marshal函数只有在转换成功的时候才会返回数据在转换的过程中我们需要注意几点
- JSON对象只支持string作为key所以要编码一个map那么必须是map[string]T这种类型(T是Go语言中任意的类型)
- Channel, complex和function是不能被编码成JSON的
- 嵌套的数据是不能编码的不然会让JSON编码进入死循环
- 指针在编码的时候会输出指针指向的内容而空指针会输出null
本小节我们介绍了如何使用Go语言的json标准包来编解码JSON数据同时也简要介绍了如何使用第三方包`go-simplejson`来在一些情况下简化操作学会并熟练运用它们将对我们接下来的Web开发相当重要。
## links
* [目录](<preface.md>)
* 上一节: [XML处理](<07.1.md>)
* 下一节: [正则处理](<07.3.md>)