Go JSON處理

2022-05-13 17:44 更新

JSON(Javascript Object Notation)是一種輕量級的數(shù)據(jù)交換語言,以文字為基礎,具有自我描述性且易于讓人閱讀。盡管JSON是Javascript的一個子集,但JSON是獨立于語言的文本格式,并且采用了類似于C語言家族的一些習慣。JSON與XML最大的不同在于XML是一個完整的標記語言,而JSON不是。JSON由于比XML更小、更快,更易解析,以及瀏覽器的內(nèi)建快速解析支持,使得其更適用于網(wǎng)絡數(shù)據(jù)傳輸領域。目前我們看到很多的開放平臺,基本上都是采用了JSON作為他們的數(shù)據(jù)交互的接口。既然JSON在Web開發(fā)中如此重要,那么Go語言對JSON支持的怎么樣呢?Go語言的標準庫已經(jīng)非常好的支持了JSON,可以很容易的對JSON數(shù)據(jù)進行編、解碼的工作。

前一小節(jié)的運維的例子用json來表示,結(jié)果描述如下:

{"servers":[{"serverName":"Shanghai_VPN","serverIP":"127.0.0.1"},{"serverName":"Beijing_VPN","serverIP":"127.0.0.2"}]}

本小節(jié)余下的內(nèi)容將以此JSON數(shù)據(jù)為基礎,來介紹go語言的json包對JSON數(shù)據(jù)的編、解碼。

解析JSON

解析到結(jié)構(gòu)體

假如有了上面的JSON串,那么我們?nèi)绾蝸斫馕鲞@個JSON串呢?Go的JSON包中有如下函數(shù)

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

通過這個函數(shù)我們就可以實現(xiàn)解析的目的,詳細的解析例子請看如下代碼:

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數(shù)據(jù)對應的結(jié)構(gòu)體,數(shù)組對應slice,字段名對應JSON里面的KEY,在解析的時候,如何將json數(shù)據(jù)與struct字段相匹配呢?例如JSON的key是Foo,那么怎么找對應的字段呢?

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

聰明的你一定注意到了這一點:能夠被賦值的字段必須是可導出字段(即首字母大寫)。同時JSON解析的時候只會解析能找得到的字段,找不到的字段會被忽略,這樣的一個好處是:當你接收到一個很大的JSON數(shù)據(jù)結(jié)構(gòu)而你卻只想獲取其中的部分數(shù)據(jù)的時候,你只需將你想要的數(shù)據(jù)對應的字段名大寫,即可輕松解決這個問題。

解析到interface

上面那種解析方式是在我們知曉被解析的JSON數(shù)據(jù)的結(jié)構(gòu)的前提下采取的方案,如果我們不知道被解析的數(shù)據(jù)的格式,又應該如何來解析呢?

我們知道interface{}可以用來存儲任意數(shù)據(jù)類型的對象,這種數(shù)據(jù)結(jié)構(gòu)正好用于存儲解析的未知結(jié)構(gòu)的json數(shù)據(jù)的結(jié)果。JSON包中采用map[string]interface{}和[]interface{}結(jié)構(gòu)來存儲任意的JSON對象和數(shù)組。Go類型和JSON類型的對應關系如下:

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

現(xiàn)在我們假設有如下的JSON數(shù)據(jù)

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

如果在我們不知道他的結(jié)構(gòu)的情況下,我們把他解析到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",
    },
}

那么如何來訪問這些數(shù)據(jù)呢?通過斷言的方式:

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

通過斷言之后,你就可以通過如下方式來訪問里面的數(shù)據(jù)了

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的配合,我們就可以解析未知結(jié)構(gòu)的JSON數(shù)了。

上面這個是官方提供的解決方案,其實很多時候我們通過類型斷言,操作起來不是很方便,目前bitly公司開源了一個叫做simplejson的包,在處理未知結(jié)構(gòu)體的JSON時相當方便,詳細例子如下所示:

js, err := NewJson([]byte(`{
    "test": {
        "array": [1, "2", 3],
        "int": 10,
        "float": 5.150,
        "bignum": 9223372036854775807,
        "string": "simplejson",
        "bool": true
    }
}`))

arr, _ := js.Get("test").Get("array").Array()
i, _ := js.Get("test").Get("int").Int()
ms := js.Get("test").Get("string").MustString()

可以看到,使用這個庫操作JSON比起官方包來說,簡單的多,詳細的請參考如下地址: https://github.com/bitly/go-simplejson

生成JSON

我們開發(fā)很多應用的時候,最后都是要輸出JSON數(shù)據(jù)串,那么如何來處理呢?JSON包里面通過Marshal函數(shù)來處理,函數(shù)定義如下:

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))
}

輸出如下內(nèi)容:

{"Servers":[{"ServerName":"Shanghai_VPN","ServerIP":"127.0.0.1"},{"ServerName":"Beijing_VPN","ServerIP":"127.0.0.2"}]}

我們看到上面的輸出字段名的首字母都是大寫的,如果你想用小寫的首字母怎么辦呢?把結(jié)構(gòu)體的字段名改成首字母小寫的?JSON輸出的時候必須注意,只有導出的字段才會被輸出,如果修改字段名,那么就會發(fā)現(xiàn)什么都不會輸出,所以必須通過struct tag定義來實現(xiàn):

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

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

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

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

  • 字段的tag是"-",那么這個字段不會輸出到JSON
  • tag中帶有自定義名稱,那么這個自定義名稱會出現(xiàn)在JSON的字段名中,例如上面例子中serverName
  • tag中如果帶有"omitempty"選項,那么如果該字段值為空,就不會輸出到JSON串中
  • 如果字段類型是bool, string, int, int64等,而tag中帶有",string"選項,那么這個字段在輸出到JSON的時候會把該字段對應的值轉(zhuǎn)換成JSON字符串

舉例來說:

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

    // ServerName 的值會進行二次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)

會輸出以下內(nèi)容:

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

Marshal函數(shù)只有在轉(zhuǎn)換成功的時候才會返回數(shù)據(jù),在轉(zhuǎn)換的過程中我們需要注意幾點:

  • JSON對象只支持string作為key,所以要編碼一個map,那么必須是map[string]T這種類型(T是Go語言中任意的類型)
  • Channel, complex和function是不能被編碼成JSON的
  • 嵌套的數(shù)據(jù)是不能編碼的,不然會讓JSON編碼進入死循環(huán)
  • 指針在編碼的時候會輸出指針指向的內(nèi)容,而空指針會輸出null

本小節(jié),我們介紹了如何使用Go語言的json標準包來編解碼JSON數(shù)據(jù),同時也簡要介紹了如何使用第三方包go-simplejson來在一些情況下簡化操作,學會并熟練運用它們將對我們接下來的Web開發(fā)相當重要。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號