12.9 JSON 數據格式
數據結構要在網絡中傳輸或保存到文件,就必須對其編碼和解碼;目前存在很多編碼格式:JSON,XML,gob,Google 緩衝協議等等。Go 語言支持所有這些編碼格式;在後面的章節,我們將討論前三種格式。
結構可能包含二進制數據,如果將其作爲文本打印,那麼可讀性是很差的。另外結構內部可能包含匿名字段,而不清楚數據的用意。
通過把數據轉換成純文本,使用命名的字段來標註,讓其具有可讀性。這樣的數據格式可以通過網絡傳輸,而且是與平臺無關的,任何類型的應用都能夠讀取和輸出,不與操作系統和編程語言的類型相關。
下面是一些術語說明:
- 數據結構 --> 指定格式 =
序列化
或編碼
(傳輸之前) - 指定格式 --> 數據格式 =
反序列化
或解碼
(傳輸之後)
序列化是在內存中把數據轉換成指定格式(data -> string),反之亦然(string -> data structure)
編碼也是一樣的,只是輸出一個數據流(實現了 io.Writer 接口);解碼是從一個數據流(實現了 io.Reader)輸出到一個數據結構。
我們都比較熟悉 XML 格式(參閱 12.10);但有些時候 JSON(JavaScript Object Notation,參閱 http://json.org)被作爲首選,主要是由於其格式上非常簡潔。通常 JSON 被用於 web 後端和瀏覽器之間的通訊,但是在其它場景也同樣的有用。
這是一個簡短的 JSON 片段:
{
"Person": {
"FirstName": "Laura",
"LastName": "Lynn"
}
}
儘管 XML 被廣泛的應用,但是 JSON 更加簡潔、輕量(佔用更少的內存、磁盤及網絡帶寬)和更好的可讀性,這也說明它越來越受歡迎。
Go 語言的 json 包可以讓你在程序中方便的讀取和寫入 JSON 數據。
我們將在下面的例子裏使用 json 包,並使用練習 10.1 vcard.go 中一個簡化版本的 Address 和 VCard 結構(爲了簡單起見,我們忽略了很多錯誤處理,不過在實際應用中你必須要合理的處理這些錯誤,參閱 13 章)
示例 12.16 json.go:
// json.go.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, 0)
defer file.Close()
enc := json.NewEncoder(file)
err := enc.Encode(vc)
if err != nil {
log.Println("Error in encoding json")
}
}
json.Marshal()
的函數簽名是 func Marshal(v interface{}) ([]byte, error)
,下面是數據編碼後的 JSON 文本(實際上是一個 []bytes):
{
"FirstName": "Jan",
"LastName": "Kersschot",
"Addresses": [{
"Type": "private",
"City": "Aartselaar",
"Country": "Belgium"
}, {
"Type": "work",
"City": "Boom",
"Country": "Belgium"
}],
"Remark": "none"
}
出於安全考慮,在 web 應用中最好使用 json.MarshalforHTML()
函數,其對數據執行HTML轉碼,所以文本可以被安全地嵌在 HTML <script>
標籤中。
JSON 與 Go 類型對應如下:
- bool 對應 JSON 的 booleans
- float64 對應 JSON 的 numbers
- string 對應 JSON 的 strings
- nil 對應 JSON 的 null
不是所有的數據都可以編碼爲 JSON 類型:只有驗證通過的數據結構才能被編碼:
- JSON 對象只支持字符串類型的 key;要編碼一個 Go map 類型,map 必須是 map[string]T(T是
json
包中支持的任何類型) - Channel,複雜類型和函數類型不能被編碼
- 不支持循環數據結構;它將引起序列化進入一個無限循環
- 指針可以被編碼,實際上是對指針指向的值進行編碼(或者指針是 nil)
反序列化:
UnMarshal()
的函數簽名是 func Unmarshal(data []byte, v interface{}) error
把 JSON 解碼爲數據結構。
我們首先創建一個結構 Message 用來保存解碼的數據:var m Message
並調用 Unmarshal()
,解析 []byte 中的 JSON 數據並將結果存入指針 m 指向的值
雖然反射能夠讓 JSON 字段去嘗試匹配目標結構字段;但是隻有真正匹配上的字段纔會填充數據。字段沒有匹配不會報錯,而是直接忽略掉。
(練習 15.2b twitter_status_json.go 中用到了 UnMarshal)
解碼任意的數據:
json 包使用 map[string]interface{}
和 []interface{}
儲存任意的 JSON 對象和數組;其可以被反序列化爲任何的 JSON blob 存儲到接口值中。
來看這個 JSON 數據,被存儲在變量 b 中:
b == []byte({"Name": "Wednesday", "Age": 6, "Parents": ["Gomez", "Morticia"]})
不用理解這個數據的結構,我們可以直接使用 Unmarshal 把這個數據編碼並保存在接口值中:
var f interface{}
err := json.Unmarshal(b, &f)
f 指向的值是一個 map,key 是一個字符串,value 是自身存儲作爲空接口類型的值:
map[string]interface{} {
"Name": "Wednesday",
"Age": 6,
"Parents": []interface{} {
"Gomez",
"Morticia",
},
}
要訪問這個數據,我們可以使用類型斷言
m := f.(map[string]interface{})
我們可以通過 for range 語法和 type switch 來訪問其實際類型:
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 數據,同時可以確保類型安全。
解碼數據到結構
如果我們事先知道 JSON 數據,我們可以定義一個適當的結構並對 JSON 數據反序列化。下面的例子中,我們將定義:
type FamilyMember struct {
Name string
Age int
Parents []string
}
並對其反序列化:
var m FamilyMember
err := json.Unmarshal(b, &m)
程序實際上是分配了一個新的切片。這是一個典型的反序列化引用類型(指針、切片和 map)的例子。
編碼和解碼流
json 包提供 Decoder 和 Encoder 類型來支持常用 JSON 數據流讀寫。NewDecoder 和 NewEncoder 函數分別封裝了 io.Reader 和 io.Writer 接口。
func NewDecoder(r io.Reader) *Decoder
func NewEncoder(w io.Writer) *Encoder
要想把 JSON 直接寫入文件,可以使用 json.NewEncoder 初始化文件(或者任何實現 io.Writer 的類型),並調用 Encode();反過來與其對應的是使用 json.Decoder 和 Decode() 函數:
func NewDecoder(r io.Reader) *Decoder
func (dec *Decoder) Decode(v interface{}) error
來看下接口是如何對實現進行抽象的:數據結構可以是任何類型,只要其實現了某種接口,目標或源數據要能夠被編碼就必須實現 io.Writer 或 io.Reader 接口。由於 Go 語言中到處都實現了 Reader 和 Writer,因此 Encoder 和 Decoder 可被應用的場景非常廣泛,例如讀取或寫入 HTTP 連接、websockets 或文件。
鏈接
- 目錄
- 上一節:使用接口的實際例子:fmt.Fprintf
- 下一節:XML 數據格式