Go的json解析:Marshal與Unmarshal


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{}類型。
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類型的切片
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字符串解碼到相應的數據結構

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

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{}變量解析后的類型:

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類型。
“簡單數據”:是指不能再進行二次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類型。如下所示:

 

type StuRead struct {
...
//普通struct類型
Class Class `json:"class"`
//指針類型
Class *Class `json:"class"`
}
// 打印效果
Class類型:{張三 18 true <nil> {1班 3} <nil>}
*Class類型:{張三 18 true <nil> 0xc42008a0c0 <nil>}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM