Go 部落格
JSON 和 Go
簡介
JSON(JavaScript 物件表示法)是一種簡易資料交換格式。其語法類似 JavaScript 的物件和清單。最常使用於網路後端與瀏覽器中執行的 JavaScript 程式之間的通訊,但也在許多其他地方使用。其首頁 json.org 提供了令人驚豔、清楚且簡潔的標準定義。
使用 json 套件,便可以從 Go 程式中輕鬆讀取和寫入 JSON 資料。
編碼
我們使用 Marshal
函式編碼 JSON 資料。
func Marshal(v interface{}) ([]byte, error)
給定 Go 資料結構 訊息
type Message struct {
Name string
Body string
Time int64
}
以及 訊息
的一個執行個體
m := Message{"Alice", "Hello", 1294706395881547000}
我們可以用 json.Marshal
彙整 m
的 JSON 編碼版本
b, err := json.Marshal(m)
一切都順利的話,err
會是 nil
,而 b
會是包含此 JSON 資料的 []byte
b == []byte(`{"Name":"Alice","Body":"Hello","Time":1294706395881547000}`)
僅會編碼可表示為有效的 JSON 的資料結構
-
JSON 物件僅支援將字串作為鍵;要編碼 Go map 型別,它必須為
map[string]T
形式(其中T
為 json 套件支援的任何 Go 型別)。 -
通道、複雜數和函式型別無法編碼。
-
不支援循環資料結構;它們將導致
Marshal
進入無限迴圈。 -
指標將編碼為它們所指向的值(如果指標為
nil
則編碼為 ’null’)。
json 套件僅存取結構型別的公開欄位(以大寫字母開頭的欄位)。因此,JSON 輸出中只會出現結構的公開欄位。
解碼
要解碼 JSON 資料,我們會使用 Unmarshal
函式。
func Unmarshal(data []byte, v interface{}) error
我們必須先建立一個儲存解碼資料的地方
var m Message
然後呼叫 json.Unmarshal
,傳遞 JSON 資料的 []byte
和指向 m
的指標
err := json.Unmarshal(b, &m)
如果 b
包含符合 m
格式的有效 JSON,則呼叫後 err
將為 nil
,且 b
中的資料將儲存在結構 m
中,就好像透過賦值一樣:
m = Message{
Name: "Alice",
Body: "Hello",
Time: 1294706395881547000,
}
Unmarshal
如何辨識用於儲存解碼資料的欄位?對於特定的 JSON 鍵 "Foo"
,Unmarshal
會從目的結構的欄位中尋找(依優先順序為):
-
具有標籤
"Foo"
的公開欄位(有關結構標籤的詳細資訊,請參閱 Go 規範), -
名為
"Foo"
的公開欄位,或 -
名為
"FOO"
或"FoO"
或一些與"Foo"
不分大小寫的配對的公開欄位。
當 JSON 資料的結構與 Go 型別不完全相符時,會發生什麼情況?
b := []byte(`{"Name":"Bob","Food":"Pickle"}`)
var m Message
err := json.Unmarshal(b, &m)
Unmarshal
僅會解碼它能在目的型別中找到的欄位。在本例中,只會填充 m
的 Name
欄位,而 Food
欄位將被忽略。此行為特別適用於您希望從大量 JSON 資料區塊中選取特定幾個欄位時。它也表示目的結構中任何未公開的欄位都不會受到 Unmarshal
影響。
但是,如果您事先不知道 JSON 資料的結構怎麼辦?
具有介面的通用 JSON
interface{}
(空介面)型別描述一個不含任何方法的介面。每個 Go 型別至少實作零個方法,因此滿足空介面。
空介面作為一個通用容器型別
var i interface{}
i = "a string"
i = 2011
i = 2.777
型別斷言存取基礎的具體型別
r := i.(float64)
fmt.Println("the circle's area", math.Pi*r*r)
或者,如果基礎型別不明,則型別轉換決定型別
switch v := i.(type) {
case int:
fmt.Println("twice i is", v*2)
case float64:
fmt.Println("the reciprocal of i is", 1/v)
case string:
h := len(v) / 2
fmt.Println("i swapped by halves is", v[h:]+v[:h])
default:
// i isn't one of the types above
}
json 套件使用 `map[string]interface{}` 和 `[]interface{}` 值來儲存任意的 JSON 物件和陣列;它會安心地解析任何有效的 JSON 資訊塊到一個一般的 `interface{}` 值。 預設的具體 Go 類型是
-
JSON 布林值的 `bool`、
-
JSON 數字的 `float64`、
-
JSON 字串的 `string`,以及
-
JSON null 的 `nil`。
解碼任意資料
考慮儲存在變數 `b` 的這個 JSON 資料
b := []byte(`{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}`)
不知道這些資料的結構,我們可以用 `Unmarshal` 將它們解碼到一個 `interface{}` 值
var f interface{}
err := json.Unmarshal(b, &f)
此時在 `f` 的 Go 值會是其鍵值為字串且其值本身儲存為空介面值的映射
f = map[string]interface{}{
"Name": "Wednesday",
"Age": 6,
"Parents": []interface{}{
"Gomez",
"Morticia",
},
}
若要存取這些資料,我們可以使用類型斷言來存取 `f` 底層的 `map[string]interface{}`
m := f.(map[string]interface{})
接著我們可以用 `range` 陳述式遍歷映射,並使用 `type switch` 來存取其值作為其具體類型
for k, v := range m {
switch vv := v.(type) {
case string:
fmt.Println(k, "is string", vv)
case float64:
fmt.Println(k, "is float64", 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 資料,同時仍然享受類型安全的優點。
參考類型
我們來定義一個 Go 類型來包含先前範例的資料
type FamilyMember struct {
Name string
Age int
Parents []string
}
var m FamilyMember
err := json.Unmarshal(b, &m)
將那些資料轉碼成 `FamilyMember` 值運作如預期,但若仔細看,我們會看到發生了一件很厲害的事。透過 `var` 陳述式,我們配置了一個 `FamilyMember` 結構,然後提供該值的指標給 `Unmarshal`,但在那個時候,`Parents` 欄位是一個 `nil` 片段值。若要填入 `Parents` 欄位,`Unmarshal` 在幕後配置了一個新的片段。這是 `Unmarshal` 與支援的參考類型(指標、片段和映射)運作的典型方式。
考慮轉碼成這個資料結構
type Foo struct {
Bar *Bar
}
若 JSON 物件有一個 `Bar` 欄位,`Unmarshal` 會配置一個新的 `Bar` 並填入。若沒有,`Bar` 會被留下來成為一個 `nil` 指標。
從這裡產生一個有用的模式:若您有一個會收到幾個不同訊息類型的應用程式,您可以定義像這樣的「接收器」結構
type IncomingMessage struct {
Cmd *Command
Msg *Message
}
而且發送者可以填入頂層 JSON 物件的 `Cmd` 欄位和/或 `Msg` 欄位,視他們要傳達的訊息種類而定。`Unmarshal` 在將 JSON 解碼成 `IncomingMessage` 結構時,只會配置出現在 JSON 資料中的資料結構。若要了解要處理哪些訊息,程式設計師只需要測試 `Cmd` 或 `Msg` 其中一個是不是 `nil` 即可。
串流編碼器和解碼器
json 套件提供 Decoder
和 Encoder
類型,以支援讀寫 JSON 資料串流的常見作業。 NewDecoder
和 NewEncoder
函式打包 io.Reader
和 io.Writer
介面類型。
func NewDecoder(r io.Reader) *Decoder
func NewEncoder(w io.Writer) *Encoder
這裡有一個範例程式,從標準輸入中讀取一系列 JSON 物件,從每個物件中去除所有欄位,但保留 Name
欄位,然後將這些物件寫入標準輸出
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 != "Name" {
delete(v, k)
}
}
if err := enc.Encode(&v); err != nil {
log.Println(err)
}
}
}
由於 Readers 和 Writers 十分普遍,所以這些 Encoder
和 Decoder
類型可廣泛用於下列情況,例如讀寫 HTTP 連線、WebSocket 或檔案。
參考文件
請參閱 json 套件文件,以取得更多資訊。請參閱 jsonrpc 套件 的原始檔,以取得使用 json 的範例。
下一篇文章: Go 越來越穩定
上一篇文章: Go 片段:使用方式和內部結構
部落格索引