Go的json解析:Marshal與Unmarshal
阿新 • • 發佈:2020-08-23
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串中匹配不了的項時,解析會自動忽略該項,該項仍保留原值。如變數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 |
// 列印效果
|