JSON(JavaScript Object Notation)是一種比XML更輕量級的資料交換格式,在易於人們閱讀和編寫的同時,也易於程式解析和生成。儘管JSON是JavaScript的一個子集,但JSON採用完全獨立於程式語言的文字格式,且表現為鍵/值對集合的文字描述形式(類似一些程式語言中的字典結構),這使它成為較為理想的、跨平臺、跨語言的資料交換語言。

開發者可以用JSON傳輸簡單的字串、數字、布林值,也可以傳輸一個數組,或者一個更復雜的複合結構。在 Web 開發領域中, JSON被廣泛應用於 Web 服務端程式和客戶端之間的資料通訊,但也不僅僅侷限於此,其應用範圍非常廣闊,比如作為Web Services API輸出的標準格式,又或是用作程式網路通訊中的遠端過程呼叫(RPC)等。

關於JSON的更多資訊,請訪問JSON官方網站

http://

json。org/

查閱。

Go語言內建對JSON的支援。使用Go語言內建的encoding/json 標準庫,開發者可以輕鬆使用Go程式生成和解析JSON格式的資料。在Go語言實現JSON的編碼和解碼時,遵循RFC4627協議標準。

1。 編碼為JSON格式

使用json。Marshal()函式可以對一組資料進行JSON格式的編碼。

json。Marshal()函式的宣告如下:

func

Marshal

v

interface

{})

([]

byte

error

假如有如下一個Book型別的結構體:

type

Book

struct

{

Title

string

Authors

[]

string

Publisher

string

IsPublished

bool

Price

float

}

並且有如下一個Book型別的例項物件:

gobook

:=

Book

{

“Go區塊鏈程式設計”

“XuShiwei”

“HughLv”

“Pandaman”

“GuaguaSong”

“HanTuo”

“BertYuan”

“XuDaoli”

],

“ituring。com。cn”

true

9。99

}

然後,我們可以使用json。Marshal()函式將gobook例項生成一段JSON格式的文字:

b

err

:=

json

Marshal

gobook

如果編碼成功, err 將賦於零值 nil,變數b 將會是一個進行JSON格式化之後的[]byte型別:

b

==

[]

byte

`{

“Title”: “Go區塊鏈程式設計”,

“Authors”: [“XuShiwei”, “HughLv”, “Pandaman”, “GuaguaSong”, “HanTuo”, “BertYuan”,

“XuDaoli”],

“Publisher”: “ituring。com。cn”,

“IsPublished”: true,

“Price”: 9。99

}`

當我們呼叫json。Marshal(gobook)語句時,會遞迴遍歷gobook物件,如果發現gobook這個資料結構實現了json。Marshaler介面且包含有效的值,Marshal()就會呼叫其MarshalJSON()方法將該資料結構生成 JSON 格式的文字。

Go語言的大多數資料型別都可以轉化為有效的JSON文字,但channel、complex和函式這幾種型別除外。

如果轉化前的資料結構中出現指標,那麼將會轉化指標所指向的值,如果指標指向的是零值,那麼null將作為轉化後的結果輸出。

在Go中, JSON轉化前後的資料型別對映如下。

布林值轉化為JSON後還是布林型別。

浮點數和整型會被轉化為JSON裡邊的常規數字。

字串將以UTF-8編碼轉化輸出為Unicode字符集的字串,特殊字元比如<將會被轉義為 。

陣列和切片會轉化為JSON裡邊的陣列,但[]byte型別的值將會被轉化為 Base64 編碼後的字串, slice型別的零值會被轉化為 null。

結構體會轉化為JSON物件,並且只有結構體裡邊以大寫字母開頭的可被匯出的欄位才會被轉化輸出,而這些可匯出的欄位會作為JSON物件的字串索引。

轉化一個map型別的資料結構時,該資料的型別必須是 map[string]T(T可以是encoding/json 包支援的任意資料型別)。

2。 解碼JSON資料

可以使用json。Unmarshal()函式將JSON格式的文字解碼為Go裡邊預期的資料結構。

json。Unmarshal()函式的原型如下:

func

Unmarshal

data

[]

byte

v

interface

{})

error

該函式的第一個引數是輸入,即JSON格式的文字(位元序列),第二個引數表示目標輸出容器,用於存放解碼後的值。要解碼一段JSON資料,首先需要在Go中建立一個目標型別的例項物件,用於存放解碼後的值:

var

book

Book

然後呼叫 json。Unmarshal() 函式,將 []byte 型別的JSON資料作為第一個引數傳入,將 book 例項變數的指標作為第二個引數傳入:

err

:=

json

Unmarshal

b

&

book

如果 b 是一個有效的JSON資料並能和book結構對應起來,那麼JSON解碼後的值將會一一存放到book結構體中。解碼成功後的 book 資料如下:

book

:=

Book

{

“Go區塊鏈程式設計”

“XuShiwei”

“HughLv”

“Pandaman”

“GuaguaSong”

“HanTuo”

“BertYuan”

“XuDaoli”

],

“ituring。com。cn”

true

9。99

}

我們不禁好奇,Go是如何將JSON資料解碼後的值一一準確無誤地關聯到一個數據結構中的相應欄位呢?

實際上, json。Unmarshal()函式會根據一個約定的順序查詢目標結構中的欄位,如果找到一個即發生匹配。假設一個JSON物件有個名為“Foo”的索引,要將“Foo”所對應的值填充到目標結構體的目標欄位上, json。Unmarshal()將會遵循如下順序進行查詢匹配:

一個包含Foo標籤的欄位;

一個名為Foo的欄位;

一個名為Foo或者Foo或者除了首字母其他字母不區分大小寫的名為Foo的欄位。

這些欄位在型別宣告中必須都是以大寫字母開頭、可被匯出的欄位。但是當JSON資料裡邊的結構和Go裡邊的目標型別的結構對不上時,會發生什麼呢?示例程式碼如下:

b

:=

[]

byte

`{“Title”: “Go區塊鏈程式設計”, “Sales”: 1000000}`

var

gobook

Book

err

:=

json

Unmarshal

b

&

gobook

如果JSON中的欄位在Go目標型別中不存在,json。Unmarshal()函式在解碼過程中會丟棄該欄位。在上面的示例程式碼中,由於Sales欄位並沒有在Book型別中定義,所以會被忽略,只有Title這個欄位的值才會被填充到gobook。Title中。

這個特性讓我們可以從同一段JSON資料中篩選指定的值填充到多個Go語言型別中。當然,前提是已知JSON資料的欄位結構。這也同樣意味著,目標型別中不可被匯出的私有欄位(非首字母大寫)將不會受到解碼轉化的影響。但如果JSON的資料結構是未知的,應該如何處理呢?

3。 解碼未知結構的JSON資料

我們已經知道,Go語言支援介面。在Go語言裡,介面是一組預定義方法的組合,任何一個型別均可透過實現介面預定義的方法來實現,且無需顯示宣告,所以沒有任何方法的空介面可以代表任何型別。換句話說,每一個型別其實都至少實現了一個空介面。

Go內建這樣靈活的型別系統,向我們傳達了一個很有價值的資訊:空介面是通用型別。如果要解碼一段未知結構的JSON,只需將這段JSON資料解碼輸出到一個空介面即可。在解碼JSON資料的過程中,JSON資料裡邊的元素型別將做如下轉換:

JSON中的布林值將會轉換為Go中的bool型別;

數值會被轉換為Go中的float64型別;

字串轉換後還是string型別;

JSON陣列會轉換為[]interface{}型別;

JSON物件會轉換為map[string]interface{}型別;

null值會轉換為nil。

在Go的標準庫encoding/json包中,允許使用map[string]interface{}和[]interface{}型別的值來分別存放未知結構的JSON物件或陣列,示例程式碼如下:

b

:=

[]

byte

`{

“Title”: “Go區塊鏈程式設計”,

“Authors”: [“XuShiwei”, “HughLv”, “Pandaman”, “GuaguaSong”, “HanTuo”, “BertYuan”,

“XuDaoli”],

“Publisher”: “ituring。com。cn”,

“IsPublished”: true,

“Price”: 9。99,

“Sales”: 1000000

}`

var

r

interface

{}

err

:=

json

Unmarshal

b

&

r

在上述程式碼中,r被定義為一個空介面。json。Unmarshal() 函式將一個JSON物件解碼到空介面r中,最終r將會是一個鍵值對的map[string]interface{} 結構:

map

string

interface

{}{

“Title”

“Go區塊鏈程式設計”

“Authors”

“XuShiwei”

“HughLv”

“Pandaman”

“GuaguaSong”

“HanTuo”

“BertYuan”

“XuDaoli”

],

“Publisher”

“ituring。com。cn”

“IsPublished”

true

“Price”

9。99

“Sales”

1000000

}

要訪問解碼後的資料結構,需要先判斷目標結構是否為預期的資料型別:

gobook

ok

:=

r

。(

map

string

interface

{})

然後,我們可以透過for迴圈搭配range語句一一訪問解碼後的目標資料:

if

ok

{

for

k

v

:=

range

gobook

{

switch

v2

:=

v

。(

type

{

case

string

fmt

Println

k

“is string”

v2

case

int

fmt

Println

k

“is int”

v2

case

bool

fmt

Println

k

“is bool”

v2

case

[]

interface

{}:

fmt

Println

k

“is an array:”

for

i

iv

:=

range

v2

{

fmt

Println

i

iv

}

default

fmt

Println

k

“is another type not handle yet”

}

}

}

雖然有些煩瑣,但的確是一種解碼未知結構的JSON資料的安全方式。

4。 JSON的流式讀寫

Go內建的encoding/json包還提供Decoder和Encoder兩個型別,用於支援JSON資料的流式讀寫,並提供NewDecoder()和NewEncoder()兩個函式來便於具體實現:

func

NewDecoder

r

io

Reader

*

Decoder

func

NewEncoder

w

io

Writer

*

Encoder

程式碼清單5-6從標準輸入流中讀取JSON資料,然後將其解碼,但只保留Title欄位(書名),再寫入到標準輸出流中。

package

main

import

“encoding/json”

“log”

“os”

func

main

()

{

dec

:=

json

NewDecoder

os

Stdin

enc

:=

json

NewEncoder

os

Stdout

for

{

var

v

map

string

interface

{}

if

err

:=

dec

Decode

&

v

);

err

!=

nil

{

log

Println

err

return

}

for

k

:=

range

v

{

if

k

!=

“Title”

{

v

k

=

nil

false

}

}

if

err

:=

enc

Encode

&

v

);

err

!=

nil

{

log

Println

err

}

}

}

使用Decoder 和Encoder對資料流進行處理可以應用得更為廣泛些,比如讀寫HTTP連線、WebSocket或檔案等,Go的標準庫net/rpc/jsonrpc就是一個應用了Decoder和Encoder的實際例子。