JSON(JavaScript Object Notation)是一种发送和接收结构化信息的标准化表示法。类似的标准化协议还有XML、ASN.1、Protobuf、Thrift等等,这些协议都有自己的鲜明特色,但是由于JSON的简洁、可读、流行性,因此它是应用最广泛的协议之一。

Go语言对上述协议的编码和解码都有很好的支持,例如标准库中的encoding/json、encoding/xml、encoding/asn1以及第三方库github.com/golang/protobuf,这些包提供的API接口都是很相似的。在本节中,我们将对encoding/json包进行讲解。

JSON用来对JavaScript的各种类型值-字符串、数字、布尔值及对象-进行Unicode文本编码。它可以对Go语言的类型进行编码:例如上一章的基本类型、slice、struct、map,而且编码后的可读性是非常好的。

基本的JSON类型有 数字类型、布尔值、字符串,其中字符串是双引号包含的Unicode字符序列并支持字符串转义。但是JSON使用\Uhhhh表示一个UTF-16编码的字符,而在Go语言中,是用rune来表示(int32,详细介绍见string一节)。

JSON的数组、对象类型是由这些基本类型组合而来的,一个JSON数组就是一个值序列,用方括号包围,值与值之间用逗号分隔,JSON数组可以用来编码Go语言中的数组和slice。一个JSON对象就是key:value键值对组成的序列,用花括号包围,键值对之间用逗号分隔,JSON对象可以用来编码Go中的map和struct。例如:

boolean         true
number          -273.15
string          "She said \"Hello, BF\""
array           ["gold", "silver", "bronze"]
object          {"year": 1980,"event": "archery","medals": ["gold", "silver", "bronze"]}

考虑一个收集电影信息并提供电影推荐的应用程序,它声明了一个Movie类型,然后初始化了一个Movie列表:

gopl.io/ch4/movie

type Movie struct {Title  stringYear   int  `json:"released"`Color  bool `json:"color,omitempty"`Actors []string
}var movies = []Movie{{Title: "Casablanca", Year: 1942, Color: false,Actors: []string{"Humphrey Bogart", "Ingrid Bergman"}},{Title: "Cool Hand Luke", Year: 1967, Color: true,Actors: []string{"Paul Newman"}},{Title: "Bullitt", Year: 1968, Color: true,Actors: []string{"Steve McQueen", "Jacqueline Bisset"}},// ...
}

这样的数据结构跟JSON的契合度会非常好,而且相互之间的转换也很容易。将Go语言中的数据结构转为JSON叫做marshal,可以通过json.Marshal函数来完成:

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

Marshal函数返回的JSON字符串是没有空白字符和缩进的,下面为了方便对JSON字符串进行折行显示:

[{"Title":"Casablanca","released":1942,"Actors":["Humphrey Bogart","Ingr
id Bergman"]},{"Title":"Cool Hand Luke","released":1967,"color":true,"Ac
tors":["Paul Newman"]},{"Title":"Bullitt","released":1968,"color":true,"
Actors":["Steve McQueen","Jacqueline Bisset"]}]

这种紧凑的表示形式是最常用的传输形式,但是不好阅读。如果需要为前端生成便于阅读的格式,可以调json.MarshaIndent,该函数有两个参数表示每一行的前缀和缩进方式:

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

输出如下(JSON的表示和Go语言有一个很大的区别,在最后一个成员后面没有逗号!!):

[{"Title": "Casablanca","released": 1942,"Actors": ["Humphrey Bogart","Ingrid Bergman"]},{"Title": "Cool Hand Luke","released": 1967,"color": true,"Actors": ["Paul Newman"]},{"Title": "Bullitt","released": 1968,"color": true,"Actors": ["Steve McQueen","Jacqueline Bisset"]}
]

在JSON编码中,默认使用struct的字段名做为JSON的对象(通过reflect技术),只有导出的字段才会被编码,这也是我们为什么使用大写字母开头的字段。

细心的读者应该已经发现:Year字段在编码后变为released,Color变为color,这个就是因为之前提到的Tag。一个struct字段的Tag是该字段的元数据:

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

Tag可以是任意的字符串,但是通常是用空格分割的key:value键值对序列,因为Tag中包含了双引号字符串,因此一般用原生字符串形式书写(见string一节)。键值对中的key为json时,对应的value值用于控制encoding/json包的编码、解码行为,并且encoding/...下面的其它包也遵循这个约定,value值的第一部分用于指定JSON对象的名字,例如将Color字段指定为color。同时Color的Tag还带了一个额外的omitempty选项,表示当Color为空或者零值时不生成JSON对象。所以,Casablanca是一部黑白电影,并没有生成color对象。

编码的逆向操作是解码,就是将JSON数据转为Go的数据结构,一般称为unmarshal,通过json.Unmarchal函数完成。下面的代码将JSON格式的电影数据解码为一个struct组成的slice,其中struct中只含有Title字段。通过定义合适的数据结构,我们可以选择性的解码JSON数据中需要的字段。当Unmarsha函数返回时,slice中包含的struct将只有Title字段,其它的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} {Bullitt}]"

很多web服务都提供了JSON格式的接口,可以通过HTTP发送JSON请求然后返回JSON格式的数据。下面,我们将通过Github的issue查询服务来演示这种用法。首先,定义合适的类型和常量:

gopl.io/ch4/github

// Package github provides a Go API for the GitHub issue tracker.
// See https://developer.github.com/v3/search/#search-issues.
package githubimport "time"const IssuesURL = "https://api.github.com/search/issues"type IssuesSearchResult struct {TotalCount int `json:"total_count"`Items          []*Issue
}type Issue struct {Number    intHTMLURL   string `json:"html_url"`Title     stringState     stringUser      *UserCreatedAt time.Time `json:"created_at"`Body      string    // in Markdown format
}type User struct {Login   stringHTMLURL string `json:"html_url"`
}

和前面一样,即使对应的JSON对象的首字母是小写的,struct的字段名也应该是首字母大写的。这些外部传入的JSON对象中,名字可能包含下划线,也可能不区分大小写,但是Go里需要大写字母开头,因此也需要用Tag来进行解码。同时,我们在这里是进行选择性的接收,因为Github返回的响应信息比我们定义的要更多。

SearchIssues函数会发起一个HTTP请求,然后解码返回的JSON数据。因为用户提供的查询条件可能包含?和&等特殊字符,为了避免URL解析问题,我用url.QueryEscape来对这些特殊字符进行转义:

gopl.io/ch4/github

package githubimport ("encoding/json""fmt""net/http""net/url""strings"
)// SearchIssues queries the GitHub issue tracker.
func SearchIssues(terms []string) (*IssuesSearchResult, error) {q := url.QueryEscape(strings.Join(terms, " "))resp, err := http.Get(IssuesURL + "?q=" + q)if err != nil {return nil, err}// We must close resp.Body on all execution paths.// (Chapter 5 presents 'defer', which makes this simpler.)if resp.StatusCode != http.StatusOK {resp.Body.Close()return nil, fmt.Errorf("search query failed: %s", resp.Status)}var result IssuesSearchResultif err := json.NewDecoder(resp.Body).Decode(&result); err != nil {resp.Body.Close()return nil, err}resp.Body.Close()return &result, nil
}

在之前的例子中,我们使用json.Unmarshal将完整的JSON字符串一次性解码为byte slice。但是这个例子中,我们使用了基于流式的解码器json.Decoder,它可以从一个输入流解码JSON数据(多次解码),当然还有一个针对输出流的json.Encoder函数。

Decode函数可以生成result,这里有多种方式可以格式化显示result。下面是最简单的一种:使用固定列数的表格,在下一节中我们将看到如何利用Go语言的标准template包来输出复杂的格式:

gopl.io/ch4/issues

// Issues prints a table of GitHub issues matching the search terms.
package mainimport ("fmt""log""os""gopl.io/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)}
}

我们的代码可以通过命令行参数指定查询条件,下面的命令会查询golang/go仓库下和json解码相关的issue BUG列表。

$ go build gopl.io/ch4/issues
$ ./issues repo:golang/go is:open json decoder
13 issues:
#5680    eaigner encoding/json: set key converter on en/decoder
#6050  gopherbot encoding/json: provide tokenizer
#8658  gopherbot encoding/json: use bufio
#8462  kortschak encoding/json: UnmarshalText confuses json.Unmarshal
#5901        rsc encoding/json: allow override type marshaling
#9812  klauspost encoding/json: string tag not symmetric
#7872  extempora encoding/json: Encoder internally buffers full output
#9650    cespare encoding/json: Decoding gives errPhase when unmarshalin
#6716  gopherbot encoding/json: include field name in unmarshal error me
#6901  lukescott encoding/json, encoding/xml: option to treat unknown fi
#6384    joeshaw encoding/json: encode precise floating point integers u
#6647    btracey x/tools/cmd/godoc: display type kind of each named type
#4237  gjemiller encoding/base64: URLEncoding padding is optional

读者可以自己尝试一下,同时Github的web接口https://developer.github.com/v3/包含了比这里演示的更多的特性。

练习 4.10: 修改issues程序,根据问题的时间进行分类,比如不到一个月的、不到一年的、超过一年。

练习 4.11: 编写一个工具,允许用户在命令行创建、读取、更新和关闭GitHub上的issue,当必要的时候自动打开用户默认的编辑器用于输入文本信息。

练习 4.12: 流行的web漫画服务xkcd也提供了JSON接口。例如,一个 https://xkcd.com/571/info.0.json请求将返回一个很多人喜爱的571编号的详细描述。下载每个链接(只下载一次)然后创建一个离线索引。编写一个xkcd工具,使用这些离线索引,打印和命令行输入的检索词相匹配的漫画的URL。

练习 4.13: 使用开放电影数据库的JSON服务接口,允许你检索和下载 https://omdbapi.com/ 上电影的名字和对应的海报图像。编写一个poster工具,通过命令行输入的电影名字,下载对应的海报。

Go语言核心之美 3.5-JSON相关推荐

  1. Go语言核心之美-必读

    Go语言核心之美开篇了!,无论你是新手还是一代高人,在这个系列文章中,总能找到你想要的! 博主是计算机领域资深专家并且是英语专8水平,翻译标准只有三个:精确.专业.不晦涩,为此每篇文章可能都要耗费数个 ...

  2. Go语言核心之美 3.4-Struct结构体

    struct(结构体)也是一种聚合的数据类型,struct可以包含多个任意类型的值,这些值被称为struct的字段.用来演示struct的一个经典案例就是雇员信息,每条雇员信息包含:员工编号,姓名,住 ...

  3. Go语言核心之美 2.5-字符串

    字符串是不可变的字节序列,虽然可以包含任意数据,包括0这个字节,不过字符串通常是用来包含可读性较强的文本.文本字符串通常采用UTF-8编码,由Unicode码点(rune)组成. 内置的len函数会返 ...

  4. Go语言核心之美 3.1-数组

    上一章我们深入学习了基本数据类型,它们是构建复杂数据类型的基础,是组成Go语言世界的原子.本章,我们将学习复合数据类型:通过不同的方式将基本类型组合起来.主要有四种复合类型--数组,切片(slice) ...

  5. Go语言核心之美 2.6-常量

    在Go语言中,常量表达式是在编译期求值的,因此在程序运行时是没有性能损耗的.常量的底层类型是前面提过的基本类型:布尔值,字符串,数值变量. 常量的声明方式和变量很相似,但是常量的值是不可变的,因此在运 ...

  6. Go语言核心之美 3.3-Map

    哈希表是一种非常好用.适用面很广的数据结构,是key-value对的无序集合.它的key是唯一的,通过key可以在常数复杂度时间内进行查询.更新或删除,无论哈希表有多大. Go语言的map类型就是对哈 ...

  7. Go语言核心之美 1.5-作用域

    变量的作用域是指程序代码中可以有效使用这个变量的范围.不要将作用域和生命期混在一起.作用域是代码中的一块区域,是一个编译期的属性:生命期是程序运行期间变量存活的时间段,在此时间段内,变量可以被程序的其 ...

  8. Go语言核心之美 1.4-包和文件

    一.Package Go语言中的包(Package)就像其它语言的库(Library)或模块(Module)一样,支持模块化,封装性,可重用性,单独编译等特点.包的源码是由数个.go文件组成,这些文件 ...

  9. Go语言核心之美 1.2-变量及声明篇

    变量 1.声明变量 使用var关键字可以创建一个指定类型的变量: var i int = 0 var i = 0 var i int 以上三个表达式均是合法的,第三个表达式会将i初始化为int类型的零 ...

  10. Go语言核心之美 2.1-整数

    第二章 序 在计算机底层,一切都是比特位.然而计算机一般操作的都是固定大小的值,称之为字(word).字会被解释为整数.浮点数.比特位数组.内存地址等,这些字又可以进一步聚合成数据包(packet). ...

最新文章

  1. windows远程ssh与scp操作linux
  2. Qt Creator调试器故障排除
  3. python简单语法_python的基本语法(一)
  4. Anatomy of a Flink Program(Flink程序的剖析)
  5. 2012年美国计算机研究生排名,2012年美国研究生留学 计算机专业院校排名TOP50
  6. MyCat 数据库分片极简体验
  7. 伦敦艺术大学创意计算机学院,伦敦艺术大学专业详细设置
  8. 通过hosts文件配置域名ip
  9. 项目管理44个过程输入输出工具技术巧记法
  10. Nginx的stub_status模块的作用及配置文件修改
  11. F5安全专栏 | 什么是零信任架构(ZTA)?
  12. 2020年度个税汇算清缴怎么办理?直接上干货!
  13. 详谈硬盘分区表格式MBR与GUID/GPT
  14. no matching distribution found for XXX 或 read timed out解决办法
  15. selenium 如何在已打开的浏览器上直接自动化脚本
  16. 聊天室php数据库,聊天室phpmysql(五)
  17. 数据结构与算法——6. 抽象数据类型:无序表与有序表及其链表实现
  18. Godaddy服务器上关于ASP.NET网站建设一些经验 - 防SQL注入攻击(三)
  19. Perl语言中一些内置变量等,$、qw、cmp、eq、ne等
  20. bal插口_麦克风的接口有哪几种?

热门文章

  1. ibm服务器修改ide,IBM刀片服务器配置IDE RAID的方法
  2. RubyonRails on linux配置
  3. Android键盘映射
  4. VB程序设计教程(第四版)龚沛曾-实验8-3
  5. 两道CTF Reverse题目(windows平台)
  6. 中了计算机病毒改怎么办,计算机中病毒了怎么办?清除计算机病毒方法有哪些...
  7. conan入门(六):conanfile.txt conanfile.py的区别
  8. 在Linux安装QQ,只需几步
  9. 银河麒麟桌面操作系统中获取硬盘序列号
  10. 华为数通HCIA学习笔记之数据通信与网络基础(二)