1. 程式人生 > 實用技巧 >Go的json解析:Marshal與Unmarshal

Go的json解析:Marshal與Unmarshal

Go的json解析:Marshal與Unmarshal

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 package main import ( "encoding/json" "fmt" ) 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() { //例項化一個數據結構,用於生成json字串 stu := Stu{ Name: "張三", Age: 18, HIgh: true, sex: "男", } //指標變數 cla := new(Class) //這個new方法,就相當於 cla := &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型別,轉化成string型別便於檢視 fmt.Println(string(jsonStu)) } //列印效果: {"name":"張三","Age":18,"HIgh":true,"class":{"Name":"1班","Grade":3}} 從結果中可以看出 只要是可匯出成員(變數首字母大寫),都可以轉成json。因成員變數sex是不可匯出的,故無法轉成json。
如果變數打上了json標籤,如Name旁邊的 `json:"name"` ,那麼轉化成的json key就用該標籤“name”,否則取變數名作為key,如“Age”,“HIgh”。 bool型別也是可以直接轉換為json的value值。Channel, complex 以及函式不能被編碼json字串。當然,迴圈的資料結構也不行,它會導致marshal陷入死迴圈。 指標變數,編碼時自動轉換為它所指向的值,如cla變數。 (當然,不傳指標,Stu struct的成員Class如果換成Class struct型別,效果也是一模一樣的。只不過指標更快,且能節省記憶體空間。) 最後,強調一句:json編碼成字串後就是純粹的字串了。

  

上面的成員變數都是已知的型別,只能接收指定的型別,比如string型別的Name只能賦值string型別的資料。 但有時為了通用性,或使程式碼簡潔,我們希望有一種型別可以接受各種型別的資料,並進行json編碼。這就用到了interface{}型別。 前言: interface{}型別其實是個空介面,即沒有方法的介面。go的每一種型別都實現了該介面。因此,任何其他型別的資料都可以賦值給interface{}型別。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 package main 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() { //例項化一個數據結構,用於生成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型別,轉化成string型別便於檢視 fmt.Println(string(jsonStu)) } //列印效果 //{"name":"張三","Age":18,"HIgh":true,"class":{"Name":"1班","Grade":3}} //從結果中可以看出,無論是string,int,bool,還是指標型別等,都可賦值給interface{}型別,且正常編碼,效果與前面的例子一樣。

  

補充: 在實際專案中,編碼成json串的資料結構,往往是切片型別。如下定義了一個[]StuRead型別的切片
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 package main import ( "encoding/json" "fmt" ) func main() { type StuRead struct { Name interface{} `json:"name"` Age interface{} HIgh interface{} sex interface{} Class interface{} `json:"class"` Test interface{} } //方式1:只宣告,不分配記憶體 var stus1 []*StuRead //方式2:分配初始值為0的記憶體 stus2 := make([]*StuRead,0) //錯誤示範 //new()只能例項化一個struct物件,而[]StuRead是切片,不是物件 stus := new([]StuRead) stu1 := &StuRead{"asd1",1,1,1,1,1} stu2 := &StuRead{"asd2",2,2,2,2,2} //由方式1和2建立的切片,都能成功追加資料 //方式2最好分配0長度,append時會自動增長。反之指定初始長度,長度不夠時不會自動增長,導致資料丟失 stus1 = append(stus1,stu1) //因為上面stus1是切片型別的結構體指標型別,所以append的型別也必須是取的地址。 stus2 = append(stus2,stu2) //因為上面stus2是切片型別的結構體指標型別,所以append的型別也必須是取的地址。 //成功編碼 json1,_ := json.Marshal(stus1) json2,_ := json.Marshal(stus2) fmt.Println(string(json1)) fmt.Println(string(json2)) } //列印效果 [{"name":"asd1","Age":1,"HIgh":1,"class":1,"Test":1}] [{"name":"asd2","Age":2,"HIgh":2,"class":2,"Test":2}]

  解碼時定義對應的切片接受即可

Json Unmarshal:將json字串解碼到相應的資料結構

我們將上面的例子進行解碼

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 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字元中的"引號,需用\進行轉義,否則編譯出錯 //json字串沿用上面的結果,但對key進行了大小的修改,並添加了sex資料 data:="{\"name\":\"張三\",\"Age\":18,\"high\":true,\"sex\":\"男\",\"CLASS\":{\"naME\":\"1班\",\"GradE\":3}}" str:=[]byte(data) //1.Unmarshal的第一個引數是json字串,第二個引數是接受json解析的資料結構。 //第二個引數必須是指標,否則無法接收解析的資料,如stu仍為空物件StuRead{} //2.可以直接stu:=new(StuRead),此時的stu自身就是指標 stu:=StuRead{} err:=json.Unmarshal(str,&stu) //解析失敗會報錯,如json字串格式不對,缺"號,缺}等。 if err!=nil{ fmt.Println(err) } fmt.Println(stu) } //列印效果 {張三 18 true <nil> map[naME:1班 GradE:3] <nil>}

  總結:

json字串解析時,需要一個“接收體”(也就是Unmarshal的第二個引數)接受解析後的資料,且Unmarshal時接收體必須傳遞指標。否則解析雖不報錯,但資料無法賦值到接受體中。如這裡用的是StuRead{}接收,就無法接收資料。 解析時,接收體可自行定義。json串中的key自動在接收體中尋找匹配的項進行賦值。匹配規則是:
  • 先查詢與key一樣的json標籤,找到則賦值給該標籤對應的變數(如Name)。
沒有json標籤的,就從上往下依次查詢變數名與key一樣的變數,如Age。或者變數名忽略大小寫後與key一樣的變 量。如HIgh,Class。第一個匹配的就賦值,後面就算有匹配的也忽略。 (前提是該變數必需是可匯出的,即首字母大寫)。 不可匯出的變數無法被解析(如sex變數,雖然json串中有key為sex的k-v,解析後其值仍為nil,即空值)
  • 當接收體中存在json串中匹配不了的項時,解析會自動忽略該項,該項仍保留原值。如變數Test,保留空值nil。
  • 你一定會發現,變數Class貌似沒有解析為我們期待樣子。因為此時的Class是個interface{}型別的變數,而json串中key為CLASS的value是個複合結構,不是可以直接解析的簡單型別資料(如“張三”,18,true等)。所以解析時,由於沒有指定變數Class的具體型別,json自動將value為複合結構的資料解析為map[string]interface{}型別的項。也就是說,此時的struct Class物件與StuRead中的Class變數沒有半毛錢關係,故與這次的json解析沒有半毛錢關係

讓我們看一下這幾個interface{}變數解析後的型別:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 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) } //結果 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>

  

從結果中可見
  • interface{}型別變數在json解析前,打印出的型別都為nil,就是沒有具體型別,這是空介面(interface{}型別)的特點。
  • json解析後,json串中value,只要是”簡單資料”,都會按照預設的型別賦值,如”張三”被賦值成string型別到Name變數中,數字18對應float64,true對應bool型別。
1 2 “簡單資料”:是指不能再進行二次json解析的資料,如”name”:”張三”只能進行一次json解析。 “複合資料”:類似”CLASS\”:{\”naME\”:\”1班\”,\”GradE\”:3}這樣的資料,是可進行二次甚至多次json解析的,因為它的value也是個可被解析的獨立json。即第一次解析key為CLASS的value,第二次解析value中的key為naME和GradE的value
  • 對於”複合資料”,如果接收體中配的項被宣告為interface{}型別,go都會預設解析成map[string]interface{}型別。如果我們想直接解析到struct Class物件中,可以將接受體對應的項定義為該struct型別。如下所示:

1 2 3 4 5 6 7 type StuRead struct { ... //普通struct型別 Class Class `json:"class"` //指標型別 Class *Class `json:"class"` }
1 // 列印效果
Class型別:{張三 18 true <nil> {1班 3} <nil>} *Class型別:{張三 18 true <nil> 0xc42008a0c0 <nil>}