數(shù)據(jù)結(jié)構(gòu)要在網(wǎng)絡(luò)中傳輸或保存到文件,就必須對(duì)其編碼和解碼;目前存在很多編碼格式:JSON,XML,gob,Google 緩沖協(xié)議等等。Go 語(yǔ)言支持所有這些編碼格式;在后面的章節(jié),我們將討論前三種格式。
結(jié)構(gòu)可能包含二進(jìn)制數(shù)據(jù),如果將其作為文本打印,那么可讀性是很差的。另外結(jié)構(gòu)內(nèi)部可能包含匿名字段,而不清楚數(shù)據(jù)的用意。
通過把數(shù)據(jù)轉(zhuǎn)換成純文本,使用命名的字段來標(biāo)注,讓其具有可讀性。這樣的數(shù)據(jù)格式可以通過網(wǎng)絡(luò)傳輸,而且是與平臺(tái)無關(guān)的,任何類型的應(yīng)用都能夠讀取和輸出,不與操作系統(tǒng)和編程語(yǔ)言的類型相關(guān)。
下面是一些術(shù)語(yǔ)說明:
序列化
或 編碼
(傳輸之前)反序列化
或 解碼
(傳輸之后)序列化是在內(nèi)存中把數(shù)據(jù)轉(zhuǎn)換成指定格式(data -> string),反之亦然(string -> data structure)
編碼也是一樣的,只是輸出一個(gè)數(shù)據(jù)流(實(shí)現(xiàn)了 io.Writer 接口);解碼是從一個(gè)數(shù)據(jù)流(實(shí)現(xiàn)了 io.Reader)輸出到一個(gè)數(shù)據(jù)結(jié)構(gòu)。
我們都比較熟悉 XML 格式(參閱 12.10);但有些時(shí)候 JSON(JavaScript Object Notation,參閱 http://json.org)被作為首選,主要是由于其格式上非常簡(jiǎn)潔。通常 JSON 被用于 web 后端和瀏覽器之間的通訊,但是在其它場(chǎng)景也同樣的有用。
這是一個(gè)簡(jiǎn)短的 JSON 片段:
{
"Person": {
"FirstName": "Laura",
"LastName": "Lynn"
}
}
盡管 XML 被廣泛的應(yīng)用,但是 JSON 更加簡(jiǎn)潔、輕量(占用更少的內(nèi)存、磁盤及網(wǎng)絡(luò)帶寬)和更好的可讀性,這也使它越來越受歡迎。
Go 語(yǔ)言的 json 包可以讓你在程序中方便的讀取和寫入 JSON 數(shù)據(jù)。
我們將在下面的例子里使用 json 包,并使用練習(xí) 10.1 vcard.go 中一個(gè)簡(jiǎn)化版本的 Address 和 VCard 結(jié)構(gòu)(為了簡(jiǎn)單起見,我們忽略了很多錯(cuò)誤處理,不過在實(shí)際應(yīng)用中你必須要合理的處理這些錯(cuò)誤,參閱 13 章)
示例 12.16 json.go:
// json.go
package main
import (
"encoding/json"
"fmt"
"log"
"os"
)
type Address struct {
Type string
City string
Country string
}
type VCard struct {
FirstName string
LastName string
Addresses []*Address
Remark string
}
func main() {
pa := &Address{"private", "Aartselaar", "Belgium"}
wa := &Address{"work", "Boom", "Belgium"}
vc := VCard{"Jan", "Kersschot", []*Address{pa, wa}, "none"}
// fmt.Printf("%v: \n", vc) // {Jan Kersschot [0x126d2b80 0x126d2be0] none}:
// JSON format:
js, _ := json.Marshal(vc)
fmt.Printf("JSON format: %s", js)
// using an encoder:
file, _ := os.OpenFile("vcard.json", os.O_CREATE|os.O_WRONLY, 0666)
defer file.Close()
enc := json.NewEncoder(file)
err := enc.Encode(vc)
if err != nil {
log.Println("Error in encoding json")
}
}
json.Marshal()
的函數(shù)簽名是 func Marshal(v interface{}) ([]byte, error)
,下面是數(shù)據(jù)編碼后的 JSON 文本(實(shí)際上是一個(gè) []byte):
{
"FirstName": "Jan",
"LastName": "Kersschot",
"Addresses": [{
"Type": "private",
"City": "Aartselaar",
"Country": "Belgium"
}, {
"Type": "work",
"City": "Boom",
"Country": "Belgium"
}],
"Remark": "none"
}
出于安全考慮,在 web 應(yīng)用中最好使用 json.MarshalforHTML()
函數(shù),其對(duì)數(shù)據(jù)執(zhí)行HTML轉(zhuǎn)碼,所以文本可以被安全地嵌在 HTML <script>
標(biāo)簽中。
json.NewEncoder()
的函數(shù)簽名是 func NewEncoder(w io.Writer) *Encoder
,返回的Encoder類型的指針可調(diào)用方法 Encode(v interface{})
,將數(shù)據(jù)對(duì)象 v 的json編碼寫入 io.Writer
w 中。
JSON 與 Go 類型對(duì)應(yīng)如下:
不是所有的數(shù)據(jù)都可以編碼為 JSON 類型:只有驗(yàn)證通過的數(shù)據(jù)結(jié)構(gòu)才能被編碼:
json
包中支持的任何類型)UnMarshal()
的函數(shù)簽名是 func Unmarshal(data []byte, v interface{}) error
把 JSON 解碼為數(shù)據(jù)結(jié)構(gòu)。
示例12.16中對(duì) vc 編碼后的數(shù)據(jù)為 js
,對(duì)其解碼時(shí),我們首先創(chuàng)建結(jié)構(gòu) VCard 用來保存解碼的數(shù)據(jù):var v VCard
并調(diào)用 json.Unmarshal(js, &v)
,解析 []byte 中的 JSON 數(shù)據(jù)并將結(jié)果存入指針 &v 指向的值。
雖然反射能夠讓 JSON 字段去嘗試匹配目標(biāo)結(jié)構(gòu)字段;但是只有真正匹配上的字段才會(huì)填充數(shù)據(jù)。字段沒有匹配不會(huì)報(bào)錯(cuò),而是直接忽略掉。
(練習(xí) 15.2b twitter_status_json.go 中用到了 UnMarshal)
json 包使用 map[string]interface{}
和 []interface{}
儲(chǔ)存任意的 JSON 對(duì)象和數(shù)組;其可以被反序列化為任何的 JSON blob 存儲(chǔ)到接口值中。
來看這個(gè) JSON 數(shù)據(jù),被存儲(chǔ)在變量 b 中:
b := []byte(`{"Name": "Wednesday", "Age": 6, "Parents": ["Gomez", "Morticia"]}`)
不用理解這個(gè)數(shù)據(jù)的結(jié)構(gòu),我們可以直接使用 Unmarshal 把這個(gè)數(shù)據(jù)編碼并保存在接口值中:
var f interface{}
err := json.Unmarshal(b, &f)
f 指向的值是一個(gè) map,key 是一個(gè)字符串,value 是自身存儲(chǔ)作為空接口類型的值:
map[string]interface{} {
"Name": "Wednesday",
"Age": 6,
"Parents": []interface{} {
"Gomez",
"Morticia",
},
}
要訪問這個(gè)數(shù)據(jù),我們可以使用類型斷言
m := f.(map[string]interface{})
我們可以通過 for range 語(yǔ)法和 type switch 來訪問其實(shí)際類型:
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 []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")
}
}
通過這種方式,你可以處理未知的 JSON 數(shù)據(jù),同時(shí)可以確保類型安全。
如果我們事先知道 JSON 數(shù)據(jù),我們可以定義一個(gè)適當(dāng)?shù)慕Y(jié)構(gòu)并對(duì) JSON 數(shù)據(jù)反序列化。下面的例子中,我們將定義:
type FamilyMember struct {
Name string
Age int
Parents []string
}
并對(duì)其反序列化:
var m FamilyMember
err := json.Unmarshal(b, &m)
程序?qū)嶋H上是分配了一個(gè)新的切片。這是一個(gè)典型的反序列化引用類型(指針、切片和 map)的例子。
json 包提供 Decoder 和 Encoder 類型來支持常用 JSON 數(shù)據(jù)流讀寫。NewDecoder 和 NewEncoder 函數(shù)分別封裝了 io.Reader 和 io.Writer 接口。
func NewDecoder(r io.Reader) *Decoder
func NewEncoder(w io.Writer) *Encoder
要想把 JSON 直接寫入文件,可以使用 json.NewEncoder 初始化文件(或者任何實(shí)現(xiàn) io.Writer 的類型),并調(diào)用 Encode();反過來與其對(duì)應(yīng)的是使用 json.Decoder 和 Decode() 函數(shù):
func NewDecoder(r io.Reader) *Decoder
func (dec *Decoder) Decode(v interface{}) error
來看下接口是如何對(duì)實(shí)現(xiàn)進(jìn)行抽象的:數(shù)據(jù)結(jié)構(gòu)可以是任何類型,只要其實(shí)現(xiàn)了某種接口,目標(biāo)或源數(shù)據(jù)要能夠被編碼就必須實(shí)現(xiàn) io.Writer 或 io.Reader 接口。由于 Go 語(yǔ)言中到處都實(shí)現(xiàn)了 Reader 和 Writer,因此 Encoder 和 Decoder 可被應(yīng)用的場(chǎng)景非常廣泛,例如讀取或?qū)懭?HTTP 連接、websockets 或文件。