Go语言学习笔记(二十)
一、处理JSON
1 JSON简介
JavaScript对象表示法(JavaScript Object Notation,JSON)是一种用于存储和交换数据的格式,这是一种人们能够理解的纯文本格式。JSON可以键值对的方式表示数据,也可以数组的方式表示数据。JSON已成为互联网上村粗he交换数据的事实标准,从很大程度上来说它已取代可拓展的标记语言(Extensible Markup Language。XML)。虽然很多现代服务依旧提供XML格式,但JSON已经是互联网上最常见的数据格式。
虽然Go语言也支持数组,但更常见的是使用切片来表示一组元素。在其它语言中,数组也被称为列表。
JSON得以流行是因为它是一种人类能够看懂的灵活而轻量级的数据格式。虽然XML提供了模式(严格的数据表示方式),但程序员完全可以随心所欲地表示数据。从表示数据所需要的字节数上说,JSON通常更为轻量级。通过诸如互联网等网络发送数据时,可能意味着应用程序的运行速度稍快。另外,JavaScript是占统治地位的Web浏览器编程环境,而JSON是JavaScript的一个子集,因此编码和解码JSON数据就是小菜一碟。
2 使用JSON API
API(应用程序接口(API:Application Program Interface))让程序员无须直接连接到数据库,就可以请求各种格式的数据并使用他们。这样的API包括以下几个:
1.纽约市交通局:通过网络提供火车、汽车和地铁交通信息,以及自动扶梯状态信息
2.英国广播公司:提供电视和广播节目播放时间表、分类细节和图片
3.Github:提供有关github.com中各种数据的信息,包括用户、组织仓库、提交和问题
4.Dark Sky:这是一个天气预报服务,通常比其他天气预报服务更准确
3 在Go语言中使用JSON
Go标准库提供了encoding/json包,可用于编码和解码JSON数据。
编码意味着将数据转换为编码后的格式,就本章而言,设施JSON格式。encoding/json包提供了函数Marshal,可用于将GO数据编码为JSON,下面显示了一个包含一些Go数据的结构体。
package main
import "fmt"
type Person struct {
Name string
Age int
Hobbies []string
}
func main() {
hobbies := []string{"Cycling", "Cheese", "Techno"}
p := Person{
Name: "George",
Age: 40,
Hobbies: hobbies,
}
fmt.Printf("%+v\n", p)
}
运行结果如下:
接下来我们尝试使用Marshal函数将其编码为JSON格式
package main
import (
"encoding/json"
"fmt"
"log"
)
type Person struct {
Name string
Age int
Hobbies []string
}
func main() {
hobbies := []string{"Cycling", "Cheese", "Techno"}
p := Person{
Name: "George",
Age: 40,
Hobbies: hobbies,
}
fmt.Printf("%+v\n", p)
//这个函数的参数是一个接口,返回值是一个字节串
//结构体实现了接口,因此将其作为参数直接传递给函数Marshal
jsonByteData, err := json.Marshal(p)
if err != nil {
log.Fatal(err)
}
//将生成的字节切片转换为字符串,并将其打印出来
jsonStringData := string(jsonByteData)
fmt.Println(jsonStringData)
}
结果如下:
存在的问题:
- 在上面例子中JSON数据中,所有的键名都以大写字母打头。但是JSON约定使用骆驼拼写法。Go语言通过给数据字段指定标签,对于JSON数据,将使用便签中的数据替换它。如下:
package main
import (
"encoding/json"
"fmt"
"log"
)
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Hobbies []string `json:"hobbies"`
}
func main() {
hobbies := []string{"Cycling", "Cheese", "Techno"}
p := Person{
Name: "George",
Age: 40,
Hobbies: hobbies,
}
fmt.Printf("%+v\n", p)
jsonByteData, err := json.Marshal(p)
if err != nil {
log.Fatal(err)
}
jsonStringData := string(jsonByteData)
fmt.Println(jsonStringData)
}
结果如下:
结构体标签还可以用于指定在编码为JSON时忽略结构体中的空字段。默认情况下,如果结构体的字段被设为空值,则编码为JSON格式后,将包含Go语言零值规则指定的值
p := Person{}
{"name":"","age":0,"hobbies":null}
忽视零值,可在JSON键名后面加上omitempty
type Person struct{
Name string `json:"name,omitempty"`
Age int `json:"age,omitempty"`
Hobbies []string `json:"hobbies,omitempty"`
}
详细示例如下:
package main
import (
"encoding/json"
"fmt"
"log"
)
type Person struct {
Name string `json:"name,omitempty"`
Age int `json:"age,omitempty"`
Hobbies []string `json:"hobbies,omitempty"`
}
func main() {
p := Person{}
jsonByteData, err := json.Marshal(p)
if err != nil {
log.Fatal(err)
}
jsonStringData := string(jsonByteData)
fmt.Println(jsonStringData)
}
运行结果如下(前者为添加omitempty
,后者没有添加):
4 解码JSON
Go语言的Unmarshal
函数提供了解码功能,它接收一个字节切片以及一个指定要将数据解码为何种格式的接口。根据数据是如何收到的,他可能是字节切片
,也可能不是。入股不是字节切片,就必须先进行转换,再将其传递给函数Unmarshal
var jsonStringData := {"name":"George","age":40,"hobbies":["Cycling","Cheese","Techno"]}
jsonByteData := []byte(jsonStringData)
与将数据编码为JSON格式一样,必须定义一个接口,以指定要将数据解码为何种格式。与将数据编码为JSON格式一样,可使用结构体标签来告诉解码器要如何将键映射到字段。
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Hobbies []string `json:"hobbies"`
}
详细示例如下:
package main
import (
"encoding/json"
"fmt"
"log"
)
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Hobbies []string `json:"hobbies"`
}
func main() {
jsonStringData := `{"name":"George","age":40,"hobbies":["Cycling","Cheese","Techno"]}`
jsonByteData := []byte(jsonStringData)
p := Person{}
err := json.Unmarshal(jsonByteData, &p)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%+v\n", p)
}
结果如下:
5 映射数据类型
编码和解码JSON时必须考虑Go和JavaScript表示数据类型的方式,我们都知道Go是一种强类型语言,而JavaScript是一种弱类型语言,即不显示的声明变量的数据类型。下面比较了Go和JavaScript中声明字符串和整型变量的方式
//JavaScript
var i=4;
var s="string";
//Go
var i int = 4
var s string = "string"
Go语言显式的声明了变量的数据类型,而JavaScript没有。由于JSON是一个JavaScript子集,因此他采用的方法和JavaScript相同:无须声明数据类型。JSON可使用的数据类型如下:
Boolean
Number
String
Array
Object
Null
JSON的数据类型不会西东映射到Go语言中的数据类型,因此encoding/json包执行显式的数据类型转换。下表显示了JSON数据类型和Go数据类型之间的对应关系。
如果在编码解码时数据类型不匹配将报错:如下
package main
import (
"encoding/json"
"fmt"
"log"
)
type Switch struct {
On bool `json:"on"`
}
func main() {
jsonStringData := `{"on":"true"}`
jsonByteData := []byte(jsonStringData)
s := Switch{}
err := json.Unmarshal(jsonByteData, &s)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%+v\n", s)
}
在这里,json中的"on"对应的值"true"是一个字符串类型
6 处理通过HTTP收到的JSON
在Go语言中,通过HTTP请求获取JSON时,收到的数据为流而不是字符串或字节切片。在这种情况下,应使用encoding/json包中的另一个方法。在这个实例中将使用Github API,这是一个很好用的API,因为它提供了格式良好的JSON,对于有些断点还无须验证身份。
res,err := http.Get("https://api.github.com/users/shapeshed")
if err != nil{
log.Fatal(err)
}
defer res.Body.Close()
由于获取的数据为流,因此可使用encoding/json包中的函数NewDecoder。这个函数接收一个io.Reader(这正是http.Get返回的类型),并返回一个Decoder。通过对返回的Decoder调用方法Decode,可将数据解码为结构体。Decode也接收一个结构体,因此必须创建一个结构体实例,并将其作为参数传递给Decode。详细使用实例如下:
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
)
type User struct {
Name string `json:"name"`
Blog string `json:"blog"`
}
func main() {
var u User
res, err := http.Get("https://api.github.com/users/shapeshed")
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
err = json.NewDecoder(res.Body).Decode(&u)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%+v\n", u)
}
7 相关问答
7.1 为编码和解码JSON,必须创建结构体,还是会自动生成结构体?
是的,要编码或解码JSON,必须创建结构体,虽然这样好像很繁琐,在JSON对象很大时尤其如此,但正如在前面的Github实例中看到的,这样可以让代码更健壮、容错能力更强。如果我们能将JSON类型映射到Go类型,就能将JSON用作数据交换格式,还可获得类型安全这样的好处。网上有多个服务可根据JSON数据自动创建Go结构
7.2 为何所有人都选择使用JSON,而不是其他数据传输格式(如XML)?
JSON是一种灵活、易学且富有表达力的数据格式。作为Web语言的JavaScript的崛起意味着在浏览器中使用JSON服务的确是很容易的。另外,JSON比其他格式占用的空间更少,这意味着可高效地传输和存储这种数据。
7.3 encoding/json包提供了验证数据有效性的途径么?
没有,encoding/json没有提供验证功能。我们可为计网构体编写用来验证数据的方法,但编码器没有这样的方法。有多个第三方结构体验证库。
7.4 必须将JSON对象中的所有数据字段都解码到结构体中么?
不是这样的,可定义只包含我们感兴趣的字段的结构体。我们可使用结构体标签来将JSON字段映射到Go结构体字段。
参考书籍
[1]: 【Go语言入门经典】[英] 乔治·奥尔波 著 张海燕 译