Go 語言 JSON 簡介
簡介
JSON (JavaScript Object Notation) 是一種輕量級的數據交換格式,因為易讀性、機器容易處理而變得流行。
JSON 語言定義的內容非常簡潔,主要分為三種類型:對象(object)、數組(array)和基本類型(value)。基本類型(value)包括:
- string 字符串,雙引號括起來的 unciode 字符序列
- number 數字,可以是整數,也可以是浮點數,但是不支持八進制和十六進制表示的數字
- true,false 真值和假值,一般對應語言中的 bool 類型
- null 空值,對應于語言中的空指針等
數組(array)就是方括號括 [] 起來的任意值的序列,中間以逗號 , 隔開。對象(object)是一系列無序的鍵值組合, 鍵必須是字符串 ,鍵值對之間以逗號 , 隔開,鍵和值以冒號 : 隔開。數組和對象中的值都可以是嵌套的。
JSON 官網 有非常易懂的圖示,進一步了解可以移步。
JSON 不依賴于任何具體的語言,但是和大多數 C 家族的編程語言數據結構特別相似,所以 JSON 成了多語言之間數據交換的流行格式。Go 語言也不例外,標準庫 encoding/json 就是專門處理 JSON 轉換的。
這篇文章就專門介紹 Go 語言中怎么和 JSON 打交道,常用的模式以及需要注意的事項。
使用
Golang 的 encoding/json 庫已經提供了很好的封裝,可以讓我們很方便地進行 JSON 數據的轉換。
Go 語言中數據結構和 JSON 類型的對應關系如下表:
| golang 類型 | JSON 類型 | 注意事項 |
|---|---|---|
| bool | JSON booleans | |
| 浮點數、整數 | JSON numbers | |
| 字符串 | JSON strings | 字符串會轉換成 UTF-8 進行輸出,無法轉換的會打印對應的 unicode 值。而且為了防止瀏覽器把 json 輸出當做 html, “<”、”>” 以及 “&” 會被轉義為 “\u003c”、”\u003e” 和 “\u0026”。 |
| array,slice | JSON arrays | []byte 會被轉換為 base64 字符串,nil slice 會被轉換為 JSON null |
| struct | JSON objects | 只有導出的字段(以大寫字母開頭)才會在輸出中 |
NOTE:Go 語言中一些特殊的類型,比如 Channel、complex、function 是不能被解析成 JSON 的。
Encode 和 Decode
要把 golang 的數據結構轉換成 JSON 字符串(encode),可以使用 Marshal 函數:
func Marshal(v interface{}) ([]byte, error)
比如我們有結構體 User
type User struct {
Name string
IsAdmin bool
Followers uint
}
以及一個實例:
user := User{
Name: "cizixs",
IsAdmin: true,
Followers: 36,
}
data, err := json.Marshal(user)
那么 data 就是 []byte 類型的數組,里面包含了解析為 JSON 之后的數據:
data == []byte(`{"Name":"cizixs","IsAdmin":true,"Followers":36}`)
相對應的,要把 JSON 數據轉換成 Go 類型的值(Decode), 可以使用 json.Unmarshal 。它的定義是這樣的:
func Unmarshal(data []byte, v interface{}) error
data 中存放的是 JSON 值,v 會存放解析后的數據,所以必須是指針,可以保證函數中做的修改能保存下來。
下面看個例子:
data = []byte(`{"Name":"gopher","IsAdmin":false,"Followers":8900}`)
var newUser = new(User)
err = json.Unmarshal(data, &newUser)
if err != nil {
fmt.Errorf("Can not decode data: %v\n", err)
}
fmt.Printf("%v\n", newUser)
那么 Unmarshal 是怎么找到結構體中對應的值呢?比如給定一個 JSON key Filed ,它是這樣查找的:
- 首先查找 tag 名字(關于 JSON tag 的解釋參看下一節)為 Field 的字段
- 然后查找名字為 Field 的字段
- 最后再找名字為 FiElD 等大小寫不敏感的匹配字段。
- 如果都沒有找到,就直接忽略這個 key,也不會報錯。這對于要從眾多數據中只選擇部分來使用非常方便。
更多控制:Tag
在定義 struct 字段的時候,可以在字段后面添加 tag,來控制 encode/decode 的過程:是否要 decode/encode 某個字段,JSON 中的字段名稱是什么。
可以選擇的控制字段有三種:
- - :不要解析這個字段
- omitempty :當字段為空(默認值)時,不要解析這個字段。比如 false、0、nil、長度為 0 的 array,map,slice,string
- FieldName :當解析 json 的時候,使用這個名字
舉例來說吧:
// 解析的時候忽略該字段。默認情況下會解析這個字段,因為它是大寫字母開頭的
Field int json:"-"
// 解析(encode/decode) 的時候,使用 other_name,而不是 Field
Field int json:"other_name"
// 解析的時候使用 other_name,如果struct 中這個值為空,就忽略它
Field int json:"other_name,omitempty"</code></pre>
解析動態內容: interface{}
上面的解析過程有一個假設——你要事先知道要解析的 JSON 內容格式,然后定義好對應的數據結構。如果你不知道要解析的內容呢? Go 提供了 interface{} 的格式,這個接口沒有限定任何的方法,因此所有的類型都是滿足這個接口的。在解析 JSON 的時候,任意動態的內容都可以解析成 interface{} 。
比如還是上面的數據,我們可以這樣做:
data := []byte({"Name":"cizixs","IsAdmin":true,"Followers":36})
var f interface{}
json.Unmarshal(data, &f)</code></pre>
但是要使用 f ,還是很麻煩的,我們要使用 type assertion :
name := f.(map[string]interface{})["Name"].(string)
對于比較復雜的結構,這樣的訪問很麻煩,也很容易出錯。
如果已經知道 JSON 數據是對象,而不是基本類型(bool,number,string,array)等,因為 JSON 對象鍵都是字符串,所以可以把上面的例子修改為:
var f map[string]interface{}
// 省去了上面 f 的 type assertion 步驟
name := f["Name"].(string)</code></pre>
需要注意的是,盡管 Followers 字段沒有小數點,我們希望它是整數值,解析的時候它還是會被解析成 float64 ,如果直接把它當做 int 訪問,會出現錯誤:
followers := f["Followers"].(int)
// panic: interface conversion: interface is float64, not int</code></pre>
而必須自己做類型轉換:
followers := int(f["Followers"].(float64))
延遲解析:json.RawMessage
在解析的時候,還可以把某部分先保留為 JSON 數據不要解析,等到后面得到更多信息的時候再去解析。繼續拿 User 舉例,比如我們要添加認證的信息,認證可以是用戶名和密碼,也可以是 token 認證。
type BasicAuth struct {
Email string
Password string
}
type TokenAuth struct {
Token string
}
type User struct {
Name string
IsAdmin bool
Followers uint
Auth json.RawMessage
}</code></pre>
我們在定義 User 結構體的時候,把認證字段的類型定義為 json.RawMessage ,這樣解析 JSON 數據的時候,對應的字段會先不急著轉換成 Go 數據結構。然后我們可以自己去再次調用 Unmarshal 去讀取里面的值:
err := json.Unmarshal(data, &basicAuth)
if basicAuth.Email != "" {
// 這是用戶名/密碼認證方式,在這里繼續做一些處理
} else {
json.Unmarshal(data, &tokenAuth)
if tokenAuth.Token != "" {
// 這是 token 認證方法
}
}
自定義解析方法
如果希望自己控制怎么解析成 JSON,或者把 JSON 解析成自定義的類型,只需要實現對應的接口(interface)。 encoding/json 提供了兩個接口: Marshaler 和 Unmarshaler :
// Marshaler 接口定義了怎么把某個類型 encode 成 JSON 數據
type Marshaler interface {
MarshalJSON() ([]byte, error)
}
// Unmarshaler 接口定義了怎么把 JSON 數據 decode 成特定的類型數據。如果后續還要使用 JSON 數據,必須把數據拷貝一份
type Unmarshaler interface {
UnmarshalJSON([]byte) error
}</code></pre>
標準庫 time.Time 就實現了這兩個接口。另外一個簡單的例子(這個例子來自于參考資料中 Go and JSON 文章):
type Month struct {
MonthNumber int
YearNumber int
}
func (m Month) MarshalJSON() ([]byte, error){
return []byte(fmt.Sprintf("%d/%d", m.MonthNumber, m.YearNumber)), nil
}
func (m *Month) UnmarshalJSON(value []byte) error {
parts := strings.Split(string(value), "/")
m.MonthNumber = strconv.ParseInt(parts[0], 10, 32)
m.YearNumber = strconv.ParseInt(parts[1], 10, 32)
return nil
}</code></pre>
和 stream 中 JSON 打交道
上面所有的 JSON 數據來源都是預先定義的 []byte 緩存,在很多時候,如果能讀取/寫入其他地方的數據就好了。 encoding/json 庫中有兩個專門處理這個事情的結構: Decoder 和 Encoder :
// Decoder 從 r io.Reader 中讀取數據,Decode(v interface{}) 方法把數據轉換成對應的數據結構
func NewDecoder(r io.Reader) *Decoder
// Encoder 的 Encode(v interface{}) 把數據結構轉換成對應的 JSON 數據,然后寫入到 w io.Writer 中
func NewEncoder(w io.Writer) *Encoder</code></pre>
下面的例子就是從標準輸入流中讀取數據,解析成數據結構,刪除所有鍵不是 Name 的字段,然后再 encode 成 JSON 數據,打印到標準輸出。
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)
}
}
}</code></pre>
參考資料
來自:http://cizixs.com/2016/12/19/golang-json-guide