JSON
http的交互的生命周期包含請求和響應。前面我們介紹了很多關于發起請求,處理請求的內容。現在該聊一聊返回響應內容了。對于web服務的響應,以前常見的響應是返回服務端渲染的模板。瀏覽器只要展示模板即可。隨著Restful風格的api出現,已經前后端分離,更多的返回格式是json字串。本節我們將討論在golang中如何編碼和解碼json。
JSON是一種數據格式描述語言。以key和value構成的哈系結構,類似javascript中的對象,python中的字典。通常json格式的key是字串,其值可以是任意類型,字串,數字,數組或者對象結構。更多關于json的可以訪問JSON了解。
數據結構map
json源于javascript的對象結構,golang中直接對應的數據結構,可是golang的map也是key-value結構,同時struct結構體也可以描述json。當然,對于json的數據類型,go也會有對象的結構所匹配。大致對應關系如下:
數據類型 | JSON | Golang |
---|---|---|
字串 | string | string |
整數 | number | int64 |
浮點數 | number | flaot64 |
數組 | arrary | slice |
對象 | object | struct |
布爾 | bool | bool |
空值 | null | nil |
基本結構編碼
golang提供了encoding/json的標準庫用于編碼json。大致需要兩步:
- 首先定義json結構體。
- 使用 Marshal方法序列化。
定義結構體的時候,只有字段名是大寫的,才會被編碼到json當中。
type Account struct {
Email string
password string
Money float64
}
func main() {
account := Account{
Email: "rsj217@gmail.com",
password: "123456",
Money: 100.5,
}
rs, err := json.Marshal(account)
if err != nil{
log.Fatalln(err)
}
fmt.Println(rs)
fmt.Println(string(rs))
}
可以看到輸出如下,Marshal方法接受一個空接口的參數,返回一個[]byte結構。小寫命名的password字段沒有被編碼到json當中,生成的json結構字段和Account結構一致。
[123 34 69 109 97 105 108 34 58 34 114 115 106 50 49 55 64 103 109 97 105 108 46 99 111 109 34 44 34 77 111 110 101 121 34 58 49 48 48 46 53 125]
{"Email":"rsj217@gmail.com","Money":100.5}
復合結構編碼
相比字串,數字等基本數據結構,slice切片,map圖則是復合結構。這些結構編碼也類似。不過map的key必須是字串,而value必須是同一類型的數據。
type User struct {
Name string
Age int
Roles []string
Skill map[string]float64
}
func main() {
skill := make(map[string]float64)
skill["python"] = 99.5
skill["elixir"] = 90
skill["ruby"] = 80.0
user := User{
Name:"rsj217",
Age: 27,
Roles: []string{"Owner", "Master"},
Skill: skill,
}
rs, err := json.Marshal(user)
if err != nil{
log.Fatalln(err)
}
fmt.Println(string(rs))
}
輸入:
{
"Name":"rsj217",
"Age":27,
"Roles":[
"Owner",
"Master"
],
"Skill":{
"elixir":90,
"python":99.5,
"ruby":80
}
}
嵌套編碼
slice和map可以匹配json的數組和對象,當前提是對象的value是同類型的情況。更通用的做法,對象的key可以是string,但是其值可以是多種結構。golang可以通過定義結構體實現這種構造:
type User struct {
Name string
Age int
Roles []string
Skill map[string]float64
Account Account
}
func main(){
...
user := User{
Name:"rsj217",
Age: 27,
Roles: []string{"Owner", "Master"},
Skill: skill,
Account:account,
}
...
}
輸出:
{
"Name":"rsj217",
"Age":27,
"Roles":[
"Owner",
"Master"
],
"Skill":{
"elixir":90,
"python":99.5,
"ruby":80
},
"Account":{
"Email":"rsj217@gmail.com",
"Money":100.5
}
}
通過定義嵌套的結構體Account,實現了key與value不一樣的結構。golang的數組或切片,其類型也是一樣的,如果遇到不同數據類型的數組,則需要借助空結構來實現:
type User struct {
...
Extra []interface{}
}
extra := []interface{}{123, "hello world"}
user := User{
...
Extra: extra,
}
輸出:
{
...
"Extra":[
123,
"hello world"
]
}
使用空接口,也可以定義像結構體實現那種不同value類型的字典結構。當空接口沒有初始化其值的時候,零值是 nil。編碼成json就是 null
type User struct {
Name string
Age int
Roles []string
Skill map[string]float64
Account Account
Extra []interface{}
Level map[string]interface{}
}
func main() {
...
level := make(map[string]interface{})
level["web"] = "Good"
level["server"] = 90
level["tool"] = nil
user := User{
Name: "rsj217",
Age: 27,
Roles: []string{"Owner", "Master"},
Skill: skill,
Account: account,
Level: level,
}
...
}
輸出:
{
...
"Extra":null,
"Level":{
"server":90,
"tool":null,
"web":"Good"
}
}
可以看到 Extra返回的并不是一個空的切片,而是null。同時Level字段實現了向字典的嵌套結構。
StructTag 字段重名
通過上面的例子,我們看到了Level
字段中的keyserver
等是小寫字母,其他的都是大寫字母。因為我們在定義結構的時候,只有使用大寫字母開頭的字段才會被導出。而通常json世界中,更盛行小寫字母的方式。看起來就成了一個矛盾。其實不然,golang提供了struct tag
的方式可以重命名結構字段的輸出形式。
type Account struct {
Email string `json:"email"`
Password string `json:"pass_word"`
Money float64 `json:"money"`
}
func main() {
account := Account{
Email: "rsj217@gmail.com",
Password: "123456",
Money: 100.5,
}
rs, err := json.Marshal(account)
...
}
我們使用struct tag,重新給Aaccount結構的字段進行了重命名。其中email小寫了,并且password字段還使用了下劃線,輸出的結果如下:
{"email":"rsj217@gmail.com","pass_word":"123456","money":100.5}
-
忽略字段
重命名的可以一個利器,這個利器還提供了更高級的選項。通常使用marshal的時候,會把結構體的所有除了私有字段都編碼到json,而實際開發中,我們定義的結構可能更通用,我們需要某個字段可以導出,但是又不能編碼到json中。
此時使用 struact tag的 -
符號就能完美解決,我們已經知道_
常用于忽略字段的占位,在tag中則使用短橫線-
。
type Account struct {
Email string `json:"email"`
Password string `json:"password,omitempty"`
Money float64 `json:"money"`
}
輸出:
{"email":"rsj217@gmail.com","money":100.5}
可見即使Password不是私有字段,因為-
忽略了它,因此沒有被編碼到json輸出。
omitempty
可選字段
對于另外一種字段,當其有值的時候就輸出,而沒有值(零值)的時候就不輸出,則可以使用另外一種選項omitempty
。
type Account struct {
Email string `json:"email"`
Password string `json:"password,omitempty"`
Money float64 `json:"money"`
}
func main() {
account := Account{
Email: "rsj217@gmail.com",
Password: "",
Money: 100.5,
}
...
}
此時password不會被編碼到json輸出中。
string
選項
gplang是靜態類型語言,對于類型定義的是不能動態修改。在json處理當中,struct tag的string可以起到部分動態類型的效果。有時候輸出的json希望是數字的字符串,而定義的字段是數字類型,那么就可以使用string選項。
type Account struct {
Email string `json:"email"`
Password string `json:"password,omitempty"`
Money float64 `json:"money,string"`
}
func main() {
account := Account{
Email: "rsj217@gmail.com",
Password: "123",
Money: 100.50,
}
...
}
可以看到輸出為 money: "100.5"
, money字段的值是字串。(其實能轉換成100.50
會比轉換成100.5
更好,可是我沒有找到通過tag的方式實現 :()。
總結
上面所介紹的大致覆蓋了golang的json編碼處理。總體原則分兩步,首先定義需要編碼的結構,然后調用encoding/json標準庫的Marshal方法生成json byte數組,轉換成string類型即可。
golang和json的大部分數據結構匹配,對于復合結構,go可以借助結構體和空接口實現json的數組和對象結構。通過struct tag可以靈活的修改json編碼的字段名和輸出控制。
既然有JSON的編碼,當然就會有JSON的解碼。相比編碼JSON,解析JSON對于golang則需要更多的技巧。下一節我們聊一聊golang解析json相關的內容。