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 或文件。

鏈接

results matching ""

    No results matching ""