您好,登錄后才能下訂單哦!
在使用 go 語(yǔ)言開(kāi)發(fā)過(guò)程中,經(jīng)常需要使用到 json 包來(lái)進(jìn)行 json 和 struct 的互相轉(zhuǎn)換,在使用過(guò)程中,遇到了一些需要額外注意的地方,記錄如下。
整數(shù)變浮點(diǎn)數(shù)問(wèn)題
假設(shè)有一個(gè) Person 結(jié)構(gòu),其中包含 Age int64 和 Weight float64 兩個(gè)字段,現(xiàn)在通過(guò) json 包將 Person 結(jié)構(gòu)轉(zhuǎn)為 map[string]interface{},代碼如下。
type Person struct { Name string Age int64 Weight float64 } func main() { person := Person{ Name: "Wang Wu", Age: 30, Weight: 150.07, } jsonBytes, _ := json.Marshal(person) fmt.Println(string(jsonBytes)) var personFromJSON interface{} json.Unmarshal(jsonBytes, &personFromJSON) r := personFromJSON.(map[string]interface{}) }
代碼執(zhí)行到這里看上去一切正常,但是打印一下 map[string]interface{} 就會(huì)發(fā)現(xiàn)不太對(duì)了。
fmt.Println(reflect.TypeOf(r["Age"]).Name()) // float64 fmt.Println(reflect.TypeOf(r["Weight"]).Name()) // float64
轉(zhuǎn)換成 map[string]interface{} 之后,原先的 uint64 和 float64 類(lèi)型都被轉(zhuǎn)換成了 float64 類(lèi)型,這顯然是不符合我們的預(yù)期的。
查看 json 的規(guī)范可以看到,在 json 中是沒(méi)有整型和浮點(diǎn)型之分的,所以現(xiàn)在可以理解 json 包中的 Unmarshal 方法轉(zhuǎn)出的數(shù)字類(lèi)型為什么都是 float64 了,因?yàn)楦鶕?jù) json 規(guī)范,數(shù)字都是同一種類(lèi)型,那么對(duì)應(yīng)到 go 的類(lèi)型中最接近的就是 float64 了。
json 包還針對(duì)這個(gè)問(wèn)題提供了更好的解決方案,不過(guò)需要使用 json.Decoder 來(lái)代替 json.Unmarshal 方法,將 json.Unmarhsal 替換如下。
var personFromJSON interface{} decoder := json.NewDecoder(bytes.NewReader(jsonBytes)) decoder.UseNumber() decoder.Decode(&personFromJSON) r := personFromJSON.(map[string]interface{})
這種方法首先創(chuàng)建了一個(gè) jsonDecoder,然后調(diào)用了 UseNumber 方法,從文檔中可以知道,使用 UseNumber 方法后,json 包會(huì)將數(shù)字轉(zhuǎn)換成一個(gè)內(nèi)置的 Number 類(lèi)型(而不是 float64),這個(gè) Number 類(lèi)型提供了轉(zhuǎn)換為 int64、float64 等多個(gè)方法。
時(shí)間格式
對(duì)于 json 格式,是沒(méi)有時(shí)間類(lèi)型的,日期和時(shí)間以 json 格式存儲(chǔ)時(shí),需要轉(zhuǎn)換為字符串類(lèi)型。這就帶來(lái)了一個(gè)問(wèn)題,日期時(shí)間的字符串表示有多種多樣,go 的 json 包支持的是哪一種呢?
使用下面的代碼來(lái)輸出 json.Marshal 方法將 Time 類(lèi)型轉(zhuǎn)換為字符串后的格式。
type Person struct { Name string Birth time.Time } func main() { person := Person{ Name: "Wang Wu", Birth: time.Now(), } jsonBytes, _ := json.Marshal(person) fmt.Println(string(jsonBytes)) // {"Name":"Wang Wu","Birth":"2018-12-20T16:22:02.00287617+08:00"} }
根據(jù)輸出可以判斷,go 的 json 包使用的是 RFC3339 標(biāo)準(zhǔn)中定義的格式。接下來(lái)測(cè)試一下 json.Unmarshal 方法所支持的日期時(shí)間格式。
dateStr := "2018-10-12" var person Person jsonStr := fmt.Sprintf("{\"name\":\"Wang Wu\", \"Birth\": \"%s\"}", dateStr) json.Unmarshal([]byte(jsonStr), &person) fmt.Println(person.Birth) // 0001-01-01 00:00:00 +0000 UTC
對(duì)于形如 2018-10-12 的字符串,json 包并沒(méi)有成功將其解析,接下來(lái)我們把 time 包中支持的所有格式都試一下。
經(jīng)過(guò)試驗(yàn),發(fā)現(xiàn) json.Unmarshal 方法只支持 RFC3339 和 RFC3339Nano 兩種格式的轉(zhuǎn)換。還有一個(gè)需要注意的地方,使用 time.Now() 生成的時(shí)間是帶有一個(gè) Monotonic Time 的,經(jīng)過(guò) json.Marshal 轉(zhuǎn)換時(shí)候,由于 RFC3339 規(guī)范里沒(méi)有存放 Monotonic Time 的位置,會(huì)丟掉這一部分。
對(duì)于字段為空的處理
json 包對(duì)于空值的處理是一個(gè)非常容易出錯(cuò)的地方,看下面代碼。
type Person struct { Name string Age int64 Birth time.Time Children []Person } func main() { person := Person{} jsonBytes, _ := json.Marshal(person) fmt.Println(string(jsonBytes)) // {"Name":"","Age":0,"Birth":"0001-01-01T00:00:00Z","Children":null} }
當(dāng) struct 中的字段沒(méi)有值時(shí),使用 json.Marshal 方法并不會(huì)自動(dòng)忽略這些字段,而是根據(jù)字段的類(lèi)型輸出了他們的默認(rèn)空值,這往往和我們的預(yù)期不一致,json 包提供了對(duì)字段的控制手段,我們可以為字段增加 omitempty tag,這個(gè) tag 會(huì)在字段值為零值(int 和 float 類(lèi)型零值是 0,string 類(lèi)型零值是 "",對(duì)象類(lèi)型零值是 nil)時(shí),忽略該字段。
type PersonAllowEmpty struct { Name string `json:",omitempty"` Age int64 `json:",omitempty"` Birth time.Time `json:",omitempty"` Children []PersonAllowEmpty `json:",omitempty"` } func main() { person := PersonAllowEmpty{} jsonBytes, _ := json.Marshal(person) fmt.Println(string(jsonBytes)) // {"Birth":"0001-01-01T00:00:00Z"} }
可以看到,這次輸出的 json 中只有 Birth 字段了,string、int、對(duì)象類(lèi)型的字段,都因?yàn)闆](méi)有賦值,默認(rèn)是零值,所以被忽略,對(duì)于日期時(shí)間類(lèi)型,由于不可以設(shè)置為零值,也就是 0000-00-00 00:00:00,不會(huì)被忽略。
需要注意這樣的情況:如果一個(gè)人的年齡是 0 (對(duì)于剛出生的嬰兒,這個(gè)值是合理的),剛好是 int 字段的零值,在添加 omitempty tag 的情況下,年齡字段會(huì)被忽略。
如果想要某一個(gè)字段在任何情況下都被 json 包忽略,需要使用如下的寫(xiě)法。
type Person struct { Name string `json:"-"` Age int64 `json:"-"` Birth time.Time `json:"-"` Children []string `json:"-"` } func main() { birth, _ := time.Parse(time.RFC3339, "1988-12-02T15:04:27+08:00") person := Person{ Name: "Wang Wu", Age: 30, Birth: birth, Children: []string{}, } jsonBytes, _ := json.Marshal(person) fmt.Println(string(jsonBytes)) // {} }
可以看到,使用 json:"-" 標(biāo)簽的字段都被忽略了。
補(bǔ)充:golang string轉(zhuǎn)json的一些坑
先看一段代碼,起作用是把字符串轉(zhuǎn)換為結(jié)構(gòu)體對(duì)應(yīng)的json
type people struct { name string `json:"name"` age int `json:"age"` id int `json:"id"` } type student struct { people id int `json:"sid"` } func main() { msg := "{\"name\":\"zhangsan\", \"age\":18, \"id\":122463, \"sid\":122464}" var someOne student if err := json.Unmarshal([]byte(msg), &someOne); err == nil { fmt.Println(someOne) fmt.Println(someOne.people) } else { fmt.Println(err) } }
仔細(xì)看看,有沒(méi)有錯(cuò)?我只能說(shuō),這樣是輸出不出來(lái)答案的,賦值錯(cuò)誤,看下面的運(yùn)行結(jié)果:
傷腦筋啊,我仔細(xì)看了半天,發(fā)現(xiàn)在定義的people和student兩個(gè)結(jié)構(gòu)體下邊有綠色的波浪線(我用的vscode),像下邊這樣:
鼠標(biāo)放上去顯示的是:
大家都知道,golang中變量聲明成大寫(xiě)和小寫(xiě)能引用的范圍是不一樣的,那我就想了,大小寫(xiě)問(wèn)題???一臉懵逼把變量名首字母改成了大寫(xiě),然后...就行了,代碼變成了下邊這樣:
type people struct { Name string `json:"name"` Age int `json:"age"` ID int `json:"id"` } type student struct { people ID int `json:"sid"` } func main() { msg := "{\"name\":\"zhangsan\", \"age\":18, \"id\":122463, \"sid\":122464}" var someOne student if err := json.Unmarshal([]byte(msg), &someOne); err == nil { fmt.Println(someOne) fmt.Println(someOne.people) } else { fmt.Println(err) } }
輸出的結(jié)果這樣:
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。