鍍金池/ 教程/ GO/ 12.9 JSON 數(shù)據(jù)格式
4.7 strings 和 strconv 包
13.6 啟動(dòng)外部命令和程序
?# 11.4 類型判斷:type-switch
12.1 讀取用戶的輸入
10.6 方法
12.2 文件讀寫
13 錯(cuò)誤處理與測(cè)試
9.3 鎖和 sync 包
12.3 文件拷貝
?# 11.7 第一個(gè)例子:使用 Sorter 接口排序
?# 11.5 測(cè)試一個(gè)值是否實(shí)現(xiàn)了某個(gè)接口
6.4 defer 和追蹤
12.10 XML 數(shù)據(jù)格式
13.10 性能調(diào)試:分析并優(yōu)化 Go 程序
?# 11.1 接口是什么
2.2 Go 環(huán)境變量
2.6 安裝目錄清單
2.5 在 Windows 上安裝 Go
11.11 Printf 和反射
1.2 語(yǔ)言的主要特性與發(fā)展的環(huán)境和影響因素
9.0 包(package)
7.4 切片重組(reslice)
13.2 運(yùn)行時(shí)異常和 panic
10.2 使用工廠方法創(chuàng)建結(jié)構(gòu)體實(shí)例
12.8 使用接口的實(shí)際例子:fmt.Fprintf
2.4 在 Mac OS X 上安裝 Go
3.8 Go 性能說明
7.2 切片
8.0 Map
3.1 Go 開發(fā)環(huán)境的基本要求
5.6 標(biāo)簽與 goto
6.10 使用閉包調(diào)試
9.5 自定義包和可見性
4.3 常量
?# 11.2 接口嵌套接口
6.5 內(nèi)置函數(shù)
前言
10.8 垃圾回收和 SetFinalizer
2.8 Go 解釋器
13.7 Go 中的單元測(cè)試和基準(zhǔn)測(cè)試
6.8 閉包
4.9 指針
13.1 錯(cuò)誤處理
10.1 結(jié)構(gòu)體定義
5.1 if-else 結(jié)構(gòu)
6.6 遞歸函數(shù)
9.9 通過 Git 打包和安裝
2.7 Go 運(yùn)行時(shí)(runtime)
10.7 類型的 String() 方法和格式化描述符
3.7 其它工具
9.6 為自定義包使用 godoc
11.12 接口與動(dòng)態(tài)類型
13.3 從 panic 中恢復(fù)(Recover)
10.3 使用自定義包中的結(jié)構(gòu)體
11.14 結(jié)構(gòu)體、集合和高階函數(shù)
3.6 生成代碼文檔
9.2 regexp 包
4.1 文件名、關(guān)鍵字與標(biāo)識(shí)符
?# 11.6 使用方法集與接口
7.0 數(shù)組與切片
7.1 聲明和初始化
12.11 用 Gob 傳輸數(shù)據(jù)
5.5 Break 與 continue
1.1 起源與發(fā)展
?# 11 接口(Interfaces)與反射(reflection)
6.9 應(yīng)用閉包:將函數(shù)作為返回值
4.2 Go 程序的基本結(jié)構(gòu)和要素
8.6 將 map 的鍵值對(duì)調(diào)
6.11 計(jì)算函數(shù)執(zhí)行時(shí)間
5.0 控制結(jié)構(gòu)
10.5 匿名字段和內(nèi)嵌結(jié)構(gòu)體
4.6 字符串
3.0 編輯器、集成開發(fā)環(huán)境與其它工具
13.8 測(cè)試的具體例子
7.6 字符串、數(shù)組和切片的應(yīng)用
8.4 map 類型的切片
3.9 與其它語(yǔ)言進(jìn)行交互
7.3 For-range 結(jié)構(gòu)
9.7 使用 go install 安裝自定義包
6.0 函數(shù)
9.8 自定義包的目錄結(jié)構(gòu)、go install 和 go test
6.3 傳遞變長(zhǎng)參數(shù)
13.9 用(測(cè)試數(shù)據(jù))表驅(qū)動(dòng)測(cè)試
11.9 空接口
8.1 聲明、初始化和 make
6.2 函數(shù)參數(shù)與返回值
9.11 在 Go 程序中使用外部庫(kù)
3.3 調(diào)試器
4.5 基本類型和運(yùn)算符
?# 11.8 第二個(gè)例子:讀和寫
12.5 用 buffer 讀取文件
總結(jié):Go 中的面向?qū)ο?/span>
11.10 反射包
12.7 用 defer 關(guān)閉文件
9.4 精密計(jì)算和 big 包
4.4 變量
6.1 介紹
13.4 自定義包中的錯(cuò)誤處理和 panicking
12.4 從命令行讀取參數(shù)
9.10 Go 的外部包和項(xiàng)目
8.3 for-range 的配套用法
3.5 格式化代碼
10.4 帶標(biāo)簽的結(jié)構(gòu)體
7.5 切片的復(fù)制與追加
?# 11.3 類型斷言:如何檢測(cè)和轉(zhuǎn)換接口變量的類型
5.4 for 結(jié)構(gòu)
4.8 時(shí)間和日期
2.3 在 Linux 上安裝 Go
12 讀寫數(shù)據(jù)
6.12 通過內(nèi)存緩存來提升性能
9.1 標(biāo)準(zhǔn)庫(kù)概述
12.6 用切片讀寫文件
10 結(jié)構(gòu)(struct)與方法(method)
8.5 map 的排序
12.9 JSON 數(shù)據(jù)格式
13.5 一種用閉包處理錯(cuò)誤的模式
3.2 編輯器和集成開發(fā)環(huán)境
12.12 Go 中的密碼學(xué)
5.2 測(cè)試多返回值函數(shù)的錯(cuò)誤
6.7 將函數(shù)作為參數(shù)
8.2 測(cè)試鍵值對(duì)是否存在及刪除元素
3.4 構(gòu)建并運(yùn)行 Go 程序
2.1 平臺(tái)與架構(gòu)
5.3 switch 結(jié)構(gòu)

12.9 JSON 數(shù)據(jù)格式

數(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ǔ)說明:

  • 數(shù)據(jù)結(jié)構(gòu) --> 指定格式 = 序列化編碼(傳輸之前)
  • 指定格式 --> 數(shù)據(jù)格式 = 反序列化解碼(傳輸之后)

序列化是在內(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)如下:

  • bool 對(duì)應(yīng) JSON 的 booleans
  • float64 對(duì)應(yīng) JSON 的 numbers
  • string 對(duì)應(yīng) JSON 的 strings
  • nil 對(duì)應(yīng) JSON 的 null

不是所有的數(shù)據(jù)都可以編碼為 JSON 類型:只有驗(yàn)證通過的數(shù)據(jù)結(jié)構(gòu)才能被編碼:

  • JSON 對(duì)象只支持字符串類型的 key;要編碼一個(gè) Go map 類型,map 必須是 map[string]T(T是 json 包中支持的任何類型)
  • Channel,復(fù)雜類型和函數(shù)類型不能被編碼
  • 不支持循環(huán)數(shù)據(jù)結(jié)構(gòu);它將引起序列化進(jìn)入一個(gè)無限循環(huán)
  • 指針可以被編碼,實(shí)際上是對(duì)指針指向的值進(jìn)行編碼(或者指針是 nil)

反序列化:

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)

解碼任意的數(shù)據(jù):

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í)可以確保類型安全。

解碼數(shù)據(jù)到結(jié)構(gòu)

如果我們事先知道 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 或文件。

鏈接