1. 程式人生 > >go的json處理

go的json處理

原文地址:https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/07.2.md

Go的JSON包中有如下函式
 func Unmarshal(data []byte, v interface{}) error
詳細的解析例子請看如下程式碼:
  package main

    import (
        "encoding/json"
        "fmt"
    )

    type Server struct {
        ServerName string
        ServerIP
string } type Serverslice struct { Servers []Server } func main() { var s Serverslice str := `{"servers":[{"serverName":"Shanghai_VPN","serverIP":"127.0.0.1"},{"serverName":"Beijing_VPN","serverIP":"127.0.0.2"}]}` json.Unmarshal([]byte(str), &s) fmt.Println
(s) }

在上面的示例程式碼中,我們首先定義了與json資料對應的結構體,陣列對應slice,欄位名對應JSON裡面的KEY,在解析的時候,如何將json資料與struct欄位相匹配呢?例如JSON的key是Foo,那麼怎麼找對應的欄位呢?

  • 首先查詢tag含有Foo的可匯出的struct欄位(首字母大寫)
  • 其次查詢欄位名是Foo的匯出欄位
  • 最後查詢類似FOO或者FoO這樣的除了首字母之外其他大小寫不敏感的匯出欄位

聰明的你一定注意到了這一點:能夠被賦值的欄位必須是可匯出欄位(即首字母大寫)。同時JSON解析的時候只會解析能找得到的欄位,找不到的欄位會被忽略,這樣的一個好處是:當你接收到一個很大的JSON資料結構而你卻只想獲取其中的部分資料的時候,你只需將你想要的資料對應的欄位名大寫,即可輕鬆解決這個問題。

解析到interface

上面那種解析方式是在我們知曉被解析的JSON資料的結構的前提下采取的方案,如果我們不知道被解析的資料的格式,又應該如何來解析呢?

我們知道interface{}可以用來儲存任意資料型別的物件,這種資料結構正好用於儲存解析的未知結構的json資料的結果。JSON包中採用map[string]interface{}和[]interface{}結構來儲存任意的JSON物件和陣列。Go型別和JSON型別的對應關係如下:

  • bool 代表 JSON booleans,
  • float64 代表 JSON numbers,
  • string 代表 JSON strings,
  • nil 代表 JSON null.

現在我們假設有如下的JSON資料

    b := []byte(`{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}`)

如果在我們不知道他的結構的情況下,我們把他解析到interface{}裡面

    var f interface{}
    err := json.Unmarshal(b, &f)

這個時候f裡面儲存了一個map型別,他們的key是string,值儲存在空的interface{}裡

    f = map[string]interface{}{
        "Name": "Wednesday",
        "Age":  6,
        "Parents": []interface{}{
            "Gomez",
            "Morticia",
        },
    }

那麼如何來訪問這些資料呢?通過斷言的方式:

    m := f.(map[string]interface{})

通過斷言之後,你就可以通過如下方式來訪問裡面的資料了

    for k, v := range m {
        switch vv := v.(type) {
        case string:
            fmt.Println(k, "is string", vv)
        case int:
            fmt.Println(k, "is int", 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")
        }
    }

通過上面的示例可以看到,通過interface{}與type assert的配合,我們就可以解析未知結構的JSON數了。

生成JSON

我們開發很多應用的時候,最後都是要輸出JSON資料串,那麼如何來處理呢?JSON包裡面通過Marshal函式來處理,函式定義如下:

    func Marshal(v interface{}) ([]byte, error)

假設我們還是需要生成上面的伺服器列表資訊,那麼如何來處理呢?請看下面的例子:

    package main

    import (
        "encoding/json"
        "fmt"
    )

    type Server struct {
        ServerName string
        ServerIP   string
    }

    type Serverslice struct {
        Servers []Server
    }

    func main() {
        var s Serverslice
        s.Servers = append(s.Servers, Server{ServerName: "Shanghai_VPN", ServerIP: "127.0.0.1"})
        s.Servers = append(s.Servers, Server{ServerName: "Beijing_VPN", ServerIP: "127.0.0.2"})
        b, err := json.Marshal(s)
        if err != nil {
            fmt.Println("json err:", err)
        }
        fmt.Println(string(b))
    }

輸出如下內容:

    {"Servers":[{"ServerName":"Shanghai_VPN","ServerIP":"127.0.0.1"},{"ServerName":"Beijing_VPN","ServerIP":"127.0.0.2"}]}
json.Marshal同樣可以序列化陣列和切片
        ids := []string{"aa", "bb", "cc"}
r,err:=json.Marshal(ids)
iferr!=nil{
returnerr
}
json.Unmarshal 可以反序列化未知型別的json串
varresultmap[string]interface{}
iferr=json.Unmarshal(resp,&result);err!=nil{
returnerr
}

我們看到上面的輸出欄位名的首字母都是大寫的,如果你想用小寫的首字母怎麼辦呢?把結構體的欄位名改成首字母小寫的?JSON輸出的時候必須注意,只有匯出的欄位才會被輸出,如果修改欄位名,那麼就會發現什麼都不會輸出,所以必須通過struct tag定義來實現:

    type Server struct {
        ServerName string `json:"serverName"`
        ServerIP   string `json:"serverIP"`
    }

    type Serverslice struct {
        Servers []Server `json:"servers"`
    }

通過修改上面的結構體定義,輸出的JSON串就和我們最開始定義的JSON串保持一致了。

針對JSON的輸出,我們在定義struct tag的時候需要注意的幾點是:

  • 欄位的tag是"-",那麼這個欄位不會輸出到JSON
  • tag中帶有自定義名稱,那麼這個自定義名稱會出現在JSON的欄位名中,例如上面例子中serverName
  • tag中如果帶有"omitempty"選項,那麼如果該欄位值為空,就不會輸出到JSON串中
  • 如果欄位型別是bool, string, int, int64等,而tag中帶有",string"選項,那麼這個欄位在輸出到JSON的時候會把該欄位對應的值轉換成JSON字串

舉例來說:

    type Server struct {
        // ID 不會匯出到JSON中
        ID int `json:"-"`

        // ServerName2 的值會進行二次JSON編碼
        ServerName  string `json:"serverName"`
        ServerName2 string `json:"serverName2,string"`

        // 如果 ServerIP 為空,則不輸出到JSON串中
        ServerIP   string `json:"serverIP,omitempty"`
    }

    s := Server {
        ID:         3,
        ServerName:  `Go "1.0" `,
        ServerName2: `Go "1.0" `,
        ServerIP:   ``,
    }
    b, _ := json.Marshal(s)
    os.Stdout.Write(b)

會輸出以下內容:

    {"serverName":"Go \"1.0\" ","serverName2":"\"Go \\\"1.0\\\" \""}

Marshal函式只有在轉換成功的時候才會返回資料,在轉換的過程中我們需要注意幾點:

  • JSON物件只支援string作為key,所以要編碼一個map,那麼必須是map[string]T這種型別(T是Go語言中任意的型別)
  • Channel, complex和function是不能被編碼成JSON的
  • 巢狀的資料是不能編碼的,不然會讓JSON編碼進入死迴圈
  • 指標在編碼的時候會輸出指標指向的內容,而空指標會輸出null

本小節,我們介紹瞭如何使用Go語言的json標準包來編解碼JSON資料,學會並熟練運用它們將對我們接下來的Web開發相當重要。