JSON(Javascript Object Notation)是一種輕量級的數據交換語言,以文字為基礎,具有自我描述性且易於讓人閱讀。盡管JSON是JavaScript的一個子集,但JSON是獨立於語言的文本格式,並且采用了類似於C語言家族的一些習慣。JSON與XML最大的不同在於XML是一個完整的標記語言,而JSON不是。JSON由於比XML更小、更快,更易解析,以及瀏覽器的內建快速解析支持,使得其更適用於網絡數據傳輸領域。
解析到結構體
Go的JSON包中有如下函數
// json.go
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)
fmt.Println(s.Servers[0].ServerIP)
}
outputs:
{[{Shanghai_VPN 127.0.0.1} {Beijing_VPN 127.0.0.2}]}
127.0.0.1
首先定義了與json數據對應的結構體,數組對應slice,字段名對應JSON里面的KEY,在解析的時候,如何將JSON數據與struct字段相匹配呢?如JSON的key是Foo,那么怎么找對應的字段呢?
- 首先查找tag含有Foo的可導出的struct字段(首字母大寫)
- 最后查找類似FOO或者FoO這樣的除了首字母之外其他大小寫不敏感的導出字段
能夠被賦值的字段必須是可導出字段(即首字母大寫)。同時JSON解析的時候只會解析能找到的字段,如果找不到的字段會被忽略,這樣的一個好處是:當你接收到一個很大的JSON數據結構而你卻只想獲取其中的部分數據的時候,你只需將你想要的數據對應的字段名大寫,即可輕松解決問題。
解析到interface
我們知道interface{}可以用來存儲任意數據類型的對象,這種數據結構正好用於存儲解析的未知結構的json數據的結果。JSON包中采用map[string]interface{}和[]interface{}結構來存儲任意的JSON對象和數組。Go類型和JSON類型的對應關系如下:
通過斷言方式訪問數據,如下例:
// tointer.go
package main
import (
"encoding/json"
"fmt"
)
func main() {
b := []byte(`{"Name":"Wednesday", "Age":6, "Parents": [ "Gomez", "Moticia" ]}`)
var f interface{}
err := json.Unmarshal(b, &f)
if err != nil {
fmt.Println(err)
}
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 []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")
}
}
}
outputs:
Name is string Wednesday
Age is of a type I don't know how to handle
Parents is an array:
0 Gomez
1 Moticia
通過上面的示例可以看到,通過interface{}與type assert的配合,我們就可以解析未知結構的JSON函數了。
生成JSON
若我們要輸出JSON數據串,可通過Marshal函數來處理
func Marshal(v interface{})([]byte, error)
例:
// generalJson
package main
import (
"encoding/json"
"fmt"
)
type Server struct {
ServerName string `json:"serverName"`
ServerIP string `json:"serverIP"`
}
type Serverslice struct {
Servers []Server `json:"servers"`
}
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))
}
outputs:
{"servers":[{"serverName":"Shanghai_VPN","serverIP":"127.0.0.1"}, {"serverName":"Beijing_VPN","serverIP":"127.0.0.2"}]}
針對JSON的輸出,我們在定義struct tag的時候需要注意幾點:
- 字段的tag是“-”,那么這個字段不會輸出到JSON
- tag中帶有自定義名稱,那么這個自定義名稱會出現在JSON的字段名中,例如上面例子中的serverName
- tag中如果帶有“omitempty”選項,那么如果該字段值為空,就不會輸出到JSON串中
- 如果字段類型是bool,string,int,int64等,而tag中帶有“,string”選項,那么這個字段在輸出到JSON的時候會把該字段對應的值轉換成JSON字符串
例:
// jsontest.go
package main
import (
"encoding/json"
"os"
)
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"`
Description string `json:"description,string"`
}
func main() {
s := Server{
ID: 3,
ServerName: `Go "1.0"`,
ServerName2: `Go "1.0"`,
ServerIP: ``,
Description: `描述信息`,
}
b, _ := json.Marshal(s)
os.Stdout.Write(b)
}
outputs:
{"serverName":"Go \"1.0\"","serverName2":"Go \"1.0\""}
Marshal函數只有在轉換成功的時候才會返回數據,在轉換的過程中我們需要注意幾點:
- JSON對象只支持string作為key,所以要編碼一個map,那么必須是map[string]T這種類型(T是Go語言中任意的類型)
- Channel,complex和function是不能被編碼成JSON的
- 嵌套的數據時不能編碼的,不然會讓JSON編碼進入死循環
- 指針在編碼的時候會輸出指針指向的內容,而空指針會輸出null
目前bitly公司開源了一個叫做simplejson的包,在處理未知結構體的JSON時相當方便,地址:https://github.com/bitly/go-simplejson