Go 中通过标准库encoding/jsonencoding.xmlencoding/asn1和其他库对 JSON、XML、ASN.1 和其他类型的标准的编码和解码提供了良好的支持,这里对使用最多的encoding/json进行一个简要地描述。

看下面一个结构体类型(Year 和 Color 后面的字符串是「成员标签」):

type Movie struct {
Title string
Yeat int `json:"released"`
Color bool `json:"Color,omitempty"`
Actors []string
} var movies = []Movie {
{
Title: "Casablanca",
Year: 1942,
Color: false,
Actors: []string{"A", "B"},
},
{
Title: "Cool Hand Luke",
Year: 1967,
Color: true,
Actors: []string{"C", "D"},
},
}

显然结构体很适合通过 JSON 来描述内部的数据,无论是从 Go 对象转换成 JSON 还是从 JSON 转换成 Go 对象都是容易的。

Go 的数据结构转换为 JSON:marshal

将 Go 的数据结构转换成 JSON 称为「marshal」,marshal 是通过json.Marshal来实现的:

data, err := json.Marshal(movies)
if err != nil {
log.Fatalf("JSON marshaling failed: %s", err)
}
fmt.Printf("%s\n", data)

Marshal 会生成一个字节 slice,其中包含一个不带有任何多余空白字符的很长的字符串。上面的结果会是这样的:

[{"Title":"Casablanca","released":1942,"Actors":["A","B"]},{"Title":"CoolHandLuke","released":1967,"color":true,"Actors":["C","D"]}]

显然上面将所有的信息都放在一行之中表示,难以阅读。为了方便阅读,MarshalIndent可以输出整齐格式化后的结果。这个函数有两个参数,一个是定义每行输出的前缀字符串,另一个是定义锁进的字符串。

data, err := json.MarshalIndent(movies, "", "    ")
if err != nil {
log.Fatalf("JSON marshaling failed: %s", err)
}
fmt.Printf("%s\n", data)

这样输出的就是清晰、易于阅读的字符串了:

[
{
"Title": "Casablanca",
"released": 1942,
"Actors": [
"A",
"B"
]
},
{
"Title": "Cool Hand Luke",
"released": 1967,
"color": true,
"Actors": [
"C",
"D"
]
}
]

可以看到 Marshal 能够使用 Go 结构体成员的名称作为 JSON 对象中字段的名称(通过反射实现),因此,只有可以导出的成员能够转换成 JSON 对象,这也是为什么一般都将 Go 结构体中的所有成员定义为首字母大写。

仔细观察上面的 JSON 对象,我们可以发现,结构体成员的Year对应地转化为了releasedColor转换为了color,这就是通过前面的成员标签field tag定义实现的。

「成员标签」定义结构体成员在编译期间关联的一些元信息:

Year int `json:"released"`
Color bool `json:"color,omitempty"`

成员标签的定义可以是任何字符串,但按照习惯,是由一串由空格分开的标签键值对key:"value"组成;因为标签的值使用双引号括起来,因此一般标签都是原生的字符串字面量。

上例中,键json控制了包encoding/json的行为,(其他encoding/...包也遵循这个规则),标签的第一部分制定了 Go 结构体成员对应 JSON 中字段的名字。成员的标签一般便是如此使用。

而 Color 标签中还有一个额外的选项:omitempty;这个选项表示:如果这个成员的值是零值或者为空,则不输出这个成员到 JSON 中。所以对于电影「Casablanse」,并没有输出成员 Color 到 JSON 中。

JSON 转换为 Go 的数据结构:unmarshal

marshal 的逆操作便是将 JSON 转换为 Go 数据结构,这个过程称为「unmarshal」,由json.Unmarshal实现,下面代码将电影的 JSON 数据转换为结构体 slice。

这里我们指定结构体中仅含 Title 这个成员,通过合理地定义 Go 的数据结构,我们可以选择解码 JSON 中哪些数据,其余数据则不会转换到结构体中:

var titles []struct{ Title string }
if err := json.Unmarshal(data, &titles); err != nil {
log.Fatalf("JSON unmarshaling failed: %s", err)
}
fmt.Println(titles) // 输出: [{Casablanca} {Cool Hand Luke}]

下面我们通过看一个例子,这个例子中我们查询一个 github 页面的 issues 接口:

1.新建一个github包,在其中定义需要的类型和常量

这里我们固定查询的 github 仓库,并定义查询结果的结构体IssuesSearchResult

// 包 github 提供 GitHub issue 跟踪接口的 Go API

/* 这里定义了查询 issuse 所用的类型和常量 */
package github import "time" const IssuesURL = "https://api.github.com/search/issues" type IssuesSearchResult struct {
TotalCount int `json:"total_count"`
Items []*Issue
} type Issue struct {
Number int
HTMLURL string `json:"html_url"`
Title string
State string
User *User
CreatedAt time.Time `json:"created_at"`
Body string // markdown 格式
} type User struct {
Login string
HTMLURL string `json:"html_url"`
}

2.编写函数SearchIssues发送 HTTP 请求,并将相应解析为 JSON

由于用户查询请求可能存在一些特殊字符,例如&?这些属于 URL 的特殊字符,因此我们还要使用url.QueryEscape来保证它们拥有正确含义:

package github

import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
) func SearchIssues(terms []string) (*IssuesSearchResult, error) {
// 发起 HTTP 请求
q := url.QueryEscape(strings.Join(terms, " "))
resp, err := http.Get(IssuesURL + "?q=" + q)
if err != nil {
return nil, err
}
// 在所有的分支上面关闭 resp.Body
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("search query failed: %s", resp.Status)
}
// 将 response 解析为 JSON
var result IssuesSearchResult
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, err
}
return &result, nil
}

不同于之前使用的json.Unmarshal,这里我们使用来流式解码器json.Decoder来对 JSON 进行解码,它能够从字节流中解码出多个 JSON 实例,相对应的,也存在用于编码的json.Encoder

3.调用之前编写的 github 包,输出查询的 issues

// 将复合搜索条件的 issues 输出为一个表格
package main import (
"fmt"
"log"
"os"
// 导入刚刚编写的包,包路径依照环境变量设置而不同
"github.com/Bylight/ch4/github"
) func main() {
// 获取结果
result, err := github.SearchIssues(os.Args[1:])
if err != nil {
log.Fatal(err)
}
// 将结果输出
fmt.Printf("%d issues:\n", result.TotalCount)
for _, item := range result.Items {
fmt.Printf("%#-5d %9.9s %.55s\n", item.Number, item.User.Login, item.Title)
}
}

最后我们运行最后编写的 main 函数,我们指定它搜索 Go 项目中 issue 跟踪接口,查找关于 JSON 编码的 Open 状态的 bug 列表:

➜  go run issues.go repo:golang/go is:open json decoder
43 issues:
33416 bserdar encoding/json: This CL adds Decoder.InternKeys
34647 babolivie encoding/json: fix byte counter increments when using d
5901 rsc encoding/json: allow override type marshaling
29035 jaswdr proposal: encoding/json: add error var to compare the
34543 maxatome encoding/json: Unmarshal & json.(*Decoder).Token report
32779 rsc proposal: encoding/json: memoize strings during decode?
28923 mvdan encoding/json: speed up the decoding scanner
11046 kurin encoding/json: Decoder internally buffers full input
...

最新文章

  1. C#中使用Linq实现全外连接
  2. C语言文件处理
  3. 面试题五 数组中出现次数超过一半的数字 时间为O(n)
  4. Python访问剪切板
  5. BizTalk开发系列(三十七) 性能监视器在BizTalk性能测试中的使用
  6. iTunesConnect进行App转移2-官方说明
  7. 常用的sql脚本 游标遍历操作
  8. (转) 新的开始之Win7、CentOS 6.4 双系统 硬盘安装
  9. WPF中viewmodel层怎样得到view层的TabControl控件对象?
  10. Linux bash shell脚本语法入门
  11. Delphi 指针大全(光看不练是学不会的)
  12. (原)ubuntu14手动安装matplotlib1.5
  13. centos7安装redis3.0和phpredis扩展详细教程(图文)
  14. python变量字符拼接
  15. 谨慎升级到HTTPS
  16. 学习 day4 html 盒子模型
  17. python day06笔记总结
  18. React项目中那些奇怪的写法
  19. 生日蛋糕 POJ - 1190 (搜索+剪枝)
  20. Java线程及线程池状态

热门文章

  1. 多线程之NSOperation简介
  2. Linux中条件语句
  3. 聊天程序——基于Socket、Thread (二)
  4. uni-app之导航配置pages.json
  5. Listener中@Autowired无法注入的问题
  6. tomcat配置虛擬路徑
  7. 多线程下,使用new实现单例
  8. Django中的日期和时间格式 DateTimeField
  9. Selenium3学习中遇到的问题
  10. pyharm无法安装包的问题