簡介Json(Javascript Object Nanotation)是一種數(shù)據(jù)交換格式,常用于前后端數(shù)據(jù)傳輸。任意一端將數(shù)據(jù)轉(zhuǎn)換成json 字符串,另一端再將該字符串解析成相應的數(shù)據(jù)結(jié)構(gòu),如string類型,strcut對象等。 go語言本身為我們提供了json的工具包”encoding/json”。
更多的使用方式,可以參考:https:///articles/6742
實現(xiàn)Json Marshal:將數(shù)據(jù)編碼成json字符串
看一個簡單的例子 type Stu struct {
Name string `json:"name"`
Age int
HIgh bool
sex string
Class *Class `json:"class"`
}
type Class struct {
Name string
Grade int
}
func main() {
//實例化一個數(shù)據(jù)結(jié)構(gòu),用于生成json字符串
stu := Stu{
Name: "張三",
Age: 18,
HIgh: true,
sex: "男",
}
//指針變量
cla := new(Class)
cla.Name = "1班"
cla.Grade = 3
stu.Class=cla
//Marshal失敗時err!=nil
jsonStu, err := json.Marshal(stu)
if err != nil {
fmt.Println("生成json字符串錯誤")
}
//jsonStu是[]byte類型,轉(zhuǎn)化成string類型便于查看
fmt.Println(string(jsonStu))
}
結(jié)果: {"name":"張三","Age":18,"HIgh":true,"class":{"Name":"1班","Grade":3}}
從結(jié)果中可以看出 只要是可導出成員(變量首字母大寫),都可以轉(zhuǎn)成json。因成員變量sex是不可導出的,故無法轉(zhuǎn)成json。 如果變量打上了json標簽,如Name旁邊的 `json:"name"` ,那么轉(zhuǎn)化成的json key就用該標簽“name”,否則取變量名作為key,如“Age”,“HIgh”。 bool類型也是可以直接轉(zhuǎn)換為json的value值。Channel, complex 以及函數(shù)不能被編碼json字符串。當然,循環(huán)的數(shù)據(jù)結(jié)構(gòu)也不行,它會導致marshal陷入死循環(huán)。 指針變量 ,編碼時自動轉(zhuǎn)換為它所指向的值 ,如cla變量。 (當然,不傳指針,Stu struct的成員Class如果換成Class struct類型,效果也是一模一樣的。只不過指針更快,且能節(jié)省內(nèi)存空間。)
最后,強調(diào)一句:json編碼成字符串后就是純粹的 字符串了。
上面的成員變量都是已知的類型,只能接收指定的類型,比如string類型的Name只能賦值string類型的數(shù)據(jù)。
但有時為了通用性,或使代碼簡潔,我們希望有一種類型可以接受各種類型的數(shù)據(jù),并進行json編碼。這就用到了interface{}類型。
前言: interface{}類型其實是個空接口,即沒有方法的接口。go的每一種類型都實現(xiàn)了該接口。因此,任何其他類型的數(shù)據(jù)都可以賦值給interface{}類型。 type Stu struct {
Name interface{} `json:"name"`
Age interface{}
HIgh interface{}
sex interface{}
Class interface{} `json:"class"`
}
type Class struct {
Name string
Grade int
}
func main() {
//與前面的例子一樣
......
}
結(jié)果: {"name":"張三","Age":18,"HIgh":true,"class":{"Name":"1班","Grade":3}}
從結(jié)果中可以看出,無論是string,int,bool,還是指針類型等,都可賦值給interface{}類型,且正常編碼,效果與前面的例子一樣。
補充: 在實際項目中,編碼成json串的數(shù)據(jù)結(jié)構(gòu),往往是切片類型。如下定義了一個[]StuRead類型的切片 //正確示范
//方式1:只聲明,不分配內(nèi)存
var stus1 []*StuRead
//方式2:分配初始值為0的內(nèi)存
stus2 := make([]*StuRead,0)
//錯誤示范
//new()只能實例化一個struct對象,而[]StuRead是切片,不是對象
stus := new([]StuRead)
stu1 := StuRead{成員賦值...}
stu2 := StuRead{成員賦值...}
//由方式1和2創(chuàng)建的切片,都能成功追加數(shù)據(jù)
//方式2最好分配0長度,append時會自動增長。反之指定初始長度,長度不夠時不會自動增長,導致數(shù)據(jù)丟失
stus1 := appen(stus1,stu1,stu2)
stus2 := appen(stus2,stu1,stu2)
//成功編碼
json1,_ := json.Marshal(stus1)
json2,_ := json.Marshal(stus2)
解碼時定義對應的切片接受即可
Json Unmarshal:將json字符串解碼到相應的數(shù)據(jù)結(jié)構(gòu)我們將上面的例子進行解碼 type StuRead struct {
Name interface{} `json:"name"`
Age interface{}
HIgh interface{}
sex interface{}
Class interface{} `json:"class"`
Test interface{}
}
type Class struct {
Name string
Grade int
}
func main() {
//json字符中的"引號,需用\進行轉(zhuǎn)義,否則編譯出錯
//json字符串沿用上面的結(jié)果,但對key進行了大小的修改,并添加了sex數(shù)據(jù)
data:="{\"name\":\"張三\",\"Age\":18,\"high\":true,\"sex\":\"男\(zhòng)",\"CLASS\":{\"naME\":\"1班\",\"GradE\":3}}"
str:=[]byte(data)
//1.Unmarshal的第一個參數(shù)是json字符串,第二個參數(shù)是接受json解析的數(shù)據(jù)結(jié)構(gòu)。
//第二個參數(shù)必須是指針,否則無法接收解析的數(shù)據(jù),如stu仍為空對象StuRead{}
//2.可以直接stu:=new(StuRead),此時的stu自身就是指針
stu:=StuRead{}
err:=json.Unmarshal(str,&stu)
//解析失敗會報錯,如json字符串格式不對,缺"號,缺}等。
if err!=nil{
fmt.Println(err)
}
fmt.Println(stu)
}
結(jié)果: {張三 18 true <nil> map[naME:1班 GradE:3] <nil>}
總結(jié): json字符串解析時,需要一個“接收體”接受解析后的數(shù)據(jù),且Unmarshal時接收體必須傳遞指針 。否則解析雖不報錯,但數(shù)據(jù)無法賦值到接受體中。如這里用的是StuRead{}接收。 解析時,接收體可自行定義。json串中的key自動在接收體中尋找匹配的項進行賦值。匹配規(guī)則是: 先查找與key一樣的json標簽 ,找到則賦值給該標簽對應的變量(如Name)。 沒有json標簽的,就從上往下依次查找變量名 與key一樣的變量,如Age?;蛘?code>變量名忽略大小寫后與key一樣的變量。如HIgh,Class。第一個匹配的就賦值,后面就算有匹配的也忽略。 (前提是該變量必需是可導出的,即首字母大寫)。
不可導出的變量無法被解析 (如sex變量,雖然json串中有key為sex的k-v,解析后其值仍為nil,即空值)
當接收體中存在json串中匹配不了的項 時,解析會自動忽略 該項,該項仍保留原值。如變量Test,保留空值nil。 你一定會發(fā)現(xiàn),變量Class貌似沒有解析為我們期待樣子。因為此時的Class是個interface{}類型的變量,而json串中key為CLASS的value是個復合結(jié)構(gòu),不是可以直接解析的簡單類型數(shù)據(jù)(如“張三”,18,true等)。所以解析時,由于沒有指定變量Class的具體類型,json自動將value為復合結(jié)構(gòu)的數(shù)據(jù)解析為map[string]interface{}類型的項。也就是說,此時的struct Class對象與StuRead中的Class變量沒有半毛錢關(guān)系,故與這次的json解析沒有半毛錢關(guān)系。
讓我們看一下這幾個interface{}變量解析后的類型 func main() {
//與前邊json解析的代碼一致
...
fmt.Println(stu) //打印json解析前變量類型
err:=json.Unmarshal(str,&stu)
fmt.Println("--------------json 解析后-----------")
...
fmt.Println(stu) //打印json解析后變量類型
}
//利用反射,打印變量類型
func printType(stu *StuRead){
nameType:=reflect.TypeOf(stu.Name)
ageType:=reflect.TypeOf(stu.Age)
highType:=reflect.TypeOf(stu.HIgh)
sexType:=reflect.TypeOf(stu.sex)
classType:=reflect.TypeOf(stu.Class)
testType:=reflect.TypeOf(stu.Test)
fmt.Println("nameType:",nameType)
fmt.Println("ageType:",ageType)
fmt.Println("highType:",highType)
fmt.Println("sexType:",sexType)
fmt.Println("classType:",classType)
fmt.Println("testType:",testType)
}
結(jié)果: nameType: <nil>
ageType: <nil>
highType: <nil>
sexType: <nil>
classType: <nil>
testType: <nil>
--------------json 解析后-----------
nameType: string
ageType: float64
highType: bool
sexType: <nil>
classType: map[string]interface {}
testType: <nil>
從結(jié)果中可見 interface{}類型變量在json解析前,打印出的類型都為nil,就是沒有具體類型,這是空接口(interface{}類型)的特點。 json解析后,json串中value,只要是”簡單數(shù)據(jù)”,都會按照默認的類型賦值,如”張三”被賦值成string類型到Name變量中,數(shù)字18對應float64,true對應bool類型。 “簡單數(shù)據(jù)”:是指不能再進行二次json解析的數(shù)據(jù),如”name”:”張三”只能進行一次json解析。
“復合數(shù)據(jù)”:類似”CLASS\”:{\”naME\”:\”1班\”,\”GradE\”:3}這樣的數(shù)據(jù),是可進行二次甚至多次json解析的,因為它的value也是個可被解析的獨立json。即第一次解析key為CLASS的value,第二次解析value中的key為naME和GradE的value
對于”復合數(shù)據(jù)”,如果接收體中配的項被聲明為interface{}類型,go都會默認解析成map[string]interface{}類型。如果我們想直接解析到struct Class對象中,可以將接受體對應的項定義為該struct類型。如下所示: type StuRead struct {
...
//普通struct類型
Class Class `json:"class"`
//指針類型
Class *Class `json:"class"`
}
stu打印結(jié)果 Class類型:{張三 18 true <nil> {1班 3} <nil>}
*Class類型:{張三 18 true <nil> 0xc42008a0c0 <nil>}
可以看出,傳遞Class類型的指針時,stu中的Class變量存的是指針,我們可通過該指針直接訪問所屬的數(shù)據(jù),如stu.Class.Name/stu.Class.Grade
Class變量解析后類型 classType: main.Class
classType: *main.Class
解析時,如果接受體中同時存在2個匹配的項,會發(fā)生什么呢? 測試1 type StuRead struct {
NAme interface{}
Name interface{}
NAMe interface{} `json:"name"`
}
結(jié)果1: //當存在匹配的json標簽時,其對應的項被賦值。
//切記:匹配的標簽可以沒有,但有時最好只有一個哦
{<nil> <nil> 張三}
測試2 type StuRead struct {
NAme interface{}
Name interface{}
NAMe interface{} `json:"name"`
NamE interface{} `json:"name"`
}
結(jié)果2 //當匹配的json標簽有多個時,標簽對應的項都不會被賦值。
//忽略標簽項,從上往下尋找第一個沒有標簽且匹配的項賦值
{張三 <nil> <nil> <nil>}
測試3 type StuRead struct {
NAme interface{}
Name interface{}
}
結(jié)果3 //沒有json標簽時,從上往下,第一個匹配的項會被賦值哦
{張三 <nil>}
測試4 type StuRead struct {
NAMe interface{} `json:"name"`
NamE interface{} `json:"name"`
}
結(jié)果4 //當相同的json標簽有多個,且沒有不帶標簽的匹配項時,報錯了哦
# command-line-arguments
src/test/b.go:48: stu.Name undefined (type *StuRead has no field or method Name, but does have NAMe)
可見,與前邊說過的匹配規(guī)則是一致的。
如果不想指定Class變量為具體的類型,仍想保留interface{}類型,但又希望該變量可以解析到struct Class對象中,這時候該怎么辦呢? 這種需求是很可能存在的,例如筆者我就碰到了
辦法還是有的,我們可以將該變量定義為json.RawMessage類型 type StuRead struct {
Name interface{}
Age interface{}
HIgh interface{}
Class json.RawMessage `json:"class"` //注意這里
}
type Class struct {
Name string
Grade int
}
func main() {
data:="{\"name\":\"張三\",\"Age\":18,\"high\":true,\"sex\":\"男\(zhòng)",\"CLASS\":{\"naME\":\"1班\",\"GradE\":3}}"
str:=[]byte(data)
stu:=StuRead{}
_:=json.Unmarshal(str,&stu)
//注意這里:二次解析!
cla:=new(Class)
json.Unmarshal(stu.Class,cla)
fmt.Println("stu:",stu)
fmt.Println("string(stu.Class):",string(stu.Class))
fmt.Println("class:",cla)
printType(&stu) //函數(shù)實現(xiàn)前面例子有
}
結(jié)果 stu: {張三 18 true [123 34 110 97 77 69 34 58 34 49 231 143 173 34 44 34 71 114 97 100 69 34 58 51 125]}
string(stu.Class): {"naME":"1班","GradE":3}
class: &{1班 3}
nameType: string
ageType: float64
highType: bool
classType: json.RawMessage
從結(jié)果中可見 接收體中,被聲明為json.RawMessage類型的變量在json解析時,變量值仍保留json的原值 ,即未被自動解析為map[string]interface{}類型。如變量Class解析后的值為:{“naME”:”1班”,”GradE”:3} 從打印的類型也可以看出,在第一次json解析時,變量Class的類型是json.RawMessage。此時,我們可以對該變量進行二次json解析,因為其值仍是個獨立且可解析的完整json串。我們只需再定義一個新的接受體即可,如json.Unmarshal(stu.Class,cla)
|