1. 程式人生 > >golang中json與struct中tag

golang中json與struct中tag

使用

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"`

    自己寫幾個小demo測試一下:

    package main
     
    import (
    	"encoding/json"
    	"fmt"
    )
     
    type peerInfo struct {
    	HTTPPort int
    	TCPPort  int
    	versiong string
    }
     
    func main() {
    	pi := peerInfo{80, 3306, "0.0.1"}
    	js, err := json.Marshal(pi)
    	if err != nil {
    		fmt.Println(err)
    	}
    	fmt.Println(string(js))
    }
    

    輸出結果:

    {"HTTPPort":80,"TCPPort":3306}  

    首字母為小寫時,為private欄位,不會轉換

    struct中新增tag:

    package main
     
    import (
    	"encoding/json"
    	"fmt"
    )
     
    type peerInfo struct {
    	HTTPPort int `json:"http_port"`
    	TCPPort  int `json:"tcp_port"`
    	versiong string
    }
     
    func main() {
    	pi := peerInfo{80, 3306, "0.0.1"}
    	js, err := json.Marshal(pi)
    	if err != nil {
    		fmt.Println(err)
    	}
    	fmt.Println(string(js))
    }
    

    輸出結果:

    {"http_port":80,"tcp_port":3306}

    json轉換成go物件:

    package main
     
    import (
    	"encoding/json"
    	"fmt"
    )
     
    type peerInfo struct {
    	HTTPPort int `json:"http_port"`
    	TCPPort  int `json:"tcp_port"`
    	versiong string
    }
     
    func main() {
    	var v peerInfo
    	data := []byte(`{"http_port":80,"tcp_port":3306}`)
    	err := json.Unmarshal(data, &v)
    	if err != nil {
    		fmt.Println(err)
    	}
    	fmt.Printf("%+v\n", v)
    }
    

    輸出結果:

    {HTTPPort:80 TCPPort:3306 versiong:}

    去除tag:

    package main
     
    import (
    	"encoding/json"
    	"fmt"
    )
     
    type peerInfo struct {
    	HTTPPort int
    	TCPPort  int
    	versiong string
    }
     
    func main() {
    	var v peerInfo
    	data := []byte(`{"http_port":80,"tcp_port":3306}`)
    	err := json.Unmarshal(data, &v)
    	if err != nil {
    		fmt.Println(err)
    	}
    	fmt.Printf("%+v\n", v)
    }
    

    得到結果:

    {HTTPPort:0 TCPPort:0 versiong:}

    無法解析到物件中。

    最後,再貼一下原文中關於json.Unmarshal的幾點:

  • 首先查詢 tag 名字(關於 JSON tag 的解釋參看下一節)為 Field 的欄位
  • 然後查詢名字為 Field 的欄位
  • 最後再找名字為 FiElD 等大小寫不敏感的匹配欄位。
  • 如果都沒有找到,就直接忽略這個 key,也不會報錯。