在實際接觸json之前,我以爲json全都是長這樣的。
{
"id":6,
"name":"hbliuu"
"marry":false
}
嗯,如果是這樣,也就沒必要寫這篇blog了。
實際上呢,第一次自己寫程序的時候,遇到的json是這樣的。
{
"name": "jero",
"parents": {
"father": "hbliuu",
"mother": "unknown"
},
"body": [
{
"height": 175
},
{
"weight": 65
}
]
}
實際比這複雜多了,但是不能直接複製過來,怕泄漏公司機密,只能自己編一個。。。
說它複雜,體現在兩點,第一,裏面有{},第二,裏面有[]。那實際是怎麼個更復雜法呢?其實就是更多的信息,更多層的嵌套。
簡單json的解析
我本來覺得這部分很簡單,想寫個demo一貼了事的,結果寫demo的過程中,碰了一鼻子灰。。。
所以還是仔細記錄一下幾個需要注意的點吧。
package main
import (
"encoding/json"
"fmt"
)
type test struct {
Id int `json:"identity"`
Name string
Marry bool
}
func main() {
data:="{\"identity\":666,\"name\":\"hbliuu\",\"marry\":false}"
str:=[]byte(data)
res:=test{}
err:=json.Unmarshal(str,&res)
if err!=nil{
fmt.Println(err)
}
fmt.Println(res)
}
上面給出了一個能用的demo,注意這個demo裏有很多細節:
1.結構體定義,每個變量首字母都要大寫
這個呢,是實踐出來的,如果沒有大寫,倒也不會報錯,只是解析出來,int就是0,string就是“”,也就是說,根本沒解析。
爲什麼會這樣呢,因爲解析的函數在json包裏,而結構體定義是在你自己寫的那個包裏,如果結構體變量首字母小寫,在json包裏就是不可見的。關於go可見性規則,推薦閱讀Golang可見性規則(公有與私有,訪問權限)。
2.結構體定義,“註釋”可以沒有
這個東西不知道官方稱呼是什麼,姑且稱之爲註釋吧,這個東西是幹什麼的呢?是用來告訴你,解析的時候,對應的字段key是什麼,那什麼時候可以沒有呢,就是你結構體變量名和json裏實際字段key的名稱一樣的時候,反之,如果不一樣,那麼必須有這個註釋,說明實際的key是什麼,不然是解析不出來的。說得可能有些抽象,看我的demo就明白了。
3.json的key不區分大小寫
也就是說,如果不管結構體裏是Name還是NaMe,效果是一樣的,都可以用來解析實際 json裏key爲name或者NAME或者任意其他大小寫組合。
複雜json的解析
複雜的json,如果還是定義結構體來解析,那結構體就得寫半天。對於json來說是括號裏套着括號,對於結構體,那就是結構體裏套着結構體。
有時候,一個複雜的json消息體裏,只有一兩個字段是我們需要的,所以沒必要定義一個結構體,把每個字段都解析出來。另外,有時候你收到的json,是一個不固定的格式,這個時候自然也沒法把json解析到定義好的結構體裏了。
不用結構體用什麼呢?我們可以看一下unmarshal函數的原型。
第二個參數,並不是結構體,而是接口,只不過在go語言裏,一切都是接口,所以往裏面傳結構體,自然也是沒問題的。
下面我們就看看,直接傳個接口,結果會是什麼樣的。首先對剛纔的demo做如下修改:
//res := test{}
var res interface{}
運行結果如下:
嗯,這個打印表示,unmarshal函數直接把json解析成了一個map。
但實際上,這個時候res還是一個interface,需要轉換成map,才能用map的操作,就像下面這樣。
if err:=json.Unmarshal(str,&res); err!=nil{
fmt.Println(err)
} else {
fmt.Println(res)
if resMap, ok := res.(map[string]interface{}); !ok {
fmt.Println("interface to map failed")
} else {
fmt.Println(resMap["name"])
}
}
結果如下:
注意,上面的類型轉換,使用的是一種安全的寫法,如果不對類型轉換的結果進行判斷,如果類型轉換出錯,就會panic。實際場景裏,你沒法保證你的程序接收到的json消息就是你預想的那種格式的,萬一別人把一個其他格式的消息不小心發送到你的程序了呢,所以,一定要對每次類型轉換的結果進行判斷!
歐克,總結一下以上的思路,首先,將json解析成一個interface,然後,使用interface的類型轉換,把interface裏面你需要的內容,給取出來。
下面就解析一下最開始給的那個複雜的例子。
一步一步來,首先,看看直接解析成interface是啥樣的。首先data改成下面這個:
dataComplex := "{" +
"\"name\": \"jero\", " +
"\"parents\": {\"" +
"father\": \"hbliuu\", " +
"\"mother\": \"unknown\"" +
"}," +
"\"body\": " +
"[" +
"{\"height\": 175}," +
"{\"weight\": 65}" +
"]" +
"}"
解析出來結果如下:
首先,整體是一個map,map裏面,body對應的value是一個數組,數組裏有兩個map,而parents對應的value又是一個 map,接下來我們解析body裏的一個字段。
首先把body解析成一個數組:
if resMap["body"] == nil {
fmt.Println("error body!")
} else if bodyArray, ok := resMap["body"].([]interface{}); !ok {
fmt.Println("interface to array failed!")
} else {
fmt.Println(bodyArray)
}
結果如下:
可以看到,這個數組由兩個map組成,接下來就是在把interface轉成map。這裏再次強調,一定做好檢查!
if resMap["body"] == nil {
fmt.Println("error body!")
} else if bodyArray, ok := resMap["body"].([]interface{}); !ok {
fmt.Println("interface to array failed!")
} else if bodyMap, ok := bodyArray[0].(map[string]interface{}); !ok {
fmt.Println("interface to map failed!")
} else if bodyMap["height"] != nil {
fmt.Println("height is:", bodyMap["height"])
}
結果如下:
到這裏,還沒完!這裏我們打印的,還是一個interface,而不是我們預想的數值類型,還需要再進行一次類型轉換,轉成float64,這樣才能滿足後續處理的需求。下面給出完整的最終版代碼。
package main
import (
"encoding/json"
"fmt"
)
type test struct {
Id int `json:"identity"`
Name string
Marry bool
}
func main() {
//data:="{\"identity\":666,\"name\":\"hbliuu\",\"marry\":false}"
dataComplex := "{" +
"\"name\": \"jero\", " +
"\"parents\": {\"" +
"father\": \"hbliuu\", " +
"\"mother\": \"unknown\"" +
"}," +
"\"body\": " +
"[" +
"{\"height\": 175}," +
"{\"weight\": 65}" +
"]" +
"}"
str:=[]byte(dataComplex)
//res := test{}
var res interface{}
if err:=json.Unmarshal(str,&res); err!=nil{
fmt.Println(err)
} else {
fmt.Println(res)
if resMap, ok := res.(map[string]interface{}); !ok {
fmt.Println("interface to map failed")
} else {
fmt.Println(resMap["name"])
if resMap["body"] == nil {
fmt.Println("error body!")
} else if bodyArray, ok := resMap["body"].([]interface{}); !ok {
fmt.Println("interface to array failed!")
} else if bodyMap, ok := bodyArray[0].(map[string]interface{}); !ok {
fmt.Println("interface to map failed!")
} else if bodyMap["height"] == nil {
fmt.Println("cannot find height!")
} else if height, ok := bodyMap["height"].(float64); !ok {
fmt.Println("height is not float64!")
} else {
fmt.Println("height is:", height)
}
}
}
}
這裏有一個疑點,height明明是175,是一個int,爲什麼要轉成float?這個呢,我也不清楚,但是!只要是數值型的,這裏就只能先把interface轉成float64,否則就會轉換失敗。
最後再次強調,類型轉換時的判斷不能省略!雖然看着上面的代碼,爲了解析一個字段寫了這麼老長,感覺有些不適,但這些代碼都是必不可少的,如果一大坨放在這你覺的不舒服,可以把接口轉換這一塊封裝到一個函數裏,但一定得有。實際生產中的代碼,我們是一定要杜絕任何可能導致panic的源頭!