午夜视频在线网站,日韩视频精品在线,中文字幕精品一区二区三区在线,在线播放精品,1024你懂我懂的旧版人,欧美日韩一级黄色片,一区二区三区在线观看视频

分享

Go的json解析:Marshal與Unmarshal

 圣地亞哥rqb0dv 2019-12-07



簡介

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ī)則是:

    1. 先查找與key一樣的json標簽,找到則賦值給該標簽對應的變量(如Name)。

    2. 沒有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)


    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多