Go 語言 JSON 簡介

yuhai4v5z 9年前發布 | 32K 次閱讀 JSON Go語言 Google Go/Golang開發

簡介

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

 

 本文由用戶 yuhai4v5z 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!