在使用 go 語言開發過程中,經常需要使用到 json 包來進行 json 和 struct 的互相轉換,在使用過程中,遇到了一些需要額外注意的地方,記錄如下。
整數變浮點數問題
假設有一個 Person 結構,其中包含 Age int64 和 Weight float64 兩個字段,現在通過 json 包將 Person 結構轉為 map[string]interface{},代碼如下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
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{}) } |
代碼執行到這里看上去一切正常,但是打印一下 map[string]interface{} 就會發現不太對了。
1
2
|
fmt.Println(reflect.TypeOf(r["Age"]).Name()) // float64 fmt.Println(reflect.TypeOf(r["Weight"]).Name()) // float64 |
轉換成 map[string]interface{} 之后,原先的 uint64 和 float64 類型都被轉換成了 float64 類型,這顯然是不符合我們的預期的。
查看 json 的規范可以看到,在 json 中是沒有整型和浮點型之分的,所以現在可以理解 json 包中的 Unmarshal 方法轉出的數字類型為什么都是 float64 了,因為根據 json 規范,數字都是同一種類型,那么對應到 go 的類型中最接近的就是 float64 了。
json 包還針對這個問題提供了更好的解決方案,不過需要使用 json.Decoder 來代替 json.Unmarshal 方法,將 json.Unmarhsal 替換如下。
1
2
3
4
5
6
7
|
var personFromJSON interface{} decoder := json.NewDecoder(bytes.NewReader(jsonBytes)) decoder.UseNumber() decoder.Decode(&personFromJSON) r := personFromJSON.(map[string]interface{}) |
這種方法首先創建了一個 jsonDecoder,然后調用了 UseNumber 方法,從文檔中可以知道,使用 UseNumber 方法后,json 包會將數字轉換成一個內置的 Number 類型(而不是 float64),這個 Number 類型提供了轉換為 int64、float64 等多個方法。
時間格式
對于 json 格式,是沒有時間類型的,日期和時間以 json 格式存儲時,需要轉換為字符串類型。這就帶來了一個問題,日期時間的字符串表示有多種多樣,go 的 json 包支持的是哪一種呢?
使用下面的代碼來輸出 json.Marshal 方法將 Time 類型轉換為字符串后的格式。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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"} } |
根據輸出可以判斷,go 的 json 包使用的是 RFC3339 標準中定義的格式。接下來測試一下 json.Unmarshal 方法所支持的日期時間格式。
1
2
3
4
5
6
7
|
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 |
對于形如 2018-10-12 的字符串,json 包并沒有成功將其解析,接下來我們把 time 包中支持的所有格式都試一下。
經過試驗,發現 json.Unmarshal 方法只支持 RFC3339 和 RFC3339Nano 兩種格式的轉換。還有一個需要注意的地方,使用 time.Now() 生成的時間是帶有一個 Monotonic Time 的,經過 json.Marshal 轉換時候,由于 RFC3339 規范里沒有存放 Monotonic Time 的位置,會丟掉這一部分。
對于字段為空的處理
json 包對于空值的處理是一個非常容易出錯的地方,看下面代碼。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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} } |
當 struct 中的字段沒有值時,使用 json.Marshal 方法并不會自動忽略這些字段,而是根據字段的類型輸出了他們的默認空值,這往往和我們的預期不一致,json 包提供了對字段的控制手段,我們可以為字段增加 omitempty tag,這個 tag 會在字段值為零值(int 和 float 類型零值是 0,string 類型零值是 "",對象類型零值是 nil)時,忽略該字段。
1
2
3
4
5
6
7
8
9
10
11
12
|
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、對象類型的字段,都因為沒有賦值,默認是零值,所以被忽略,對于日期時間類型,由于不可以設置為零值,也就是 0000-00-00 00:00:00,不會被忽略。
需要注意這樣的情況:如果一個人的年齡是 0 (對于剛出生的嬰兒,這個值是合理的),剛好是 int 字段的零值,在添加 omitempty tag 的情況下,年齡字段會被忽略。
如果想要某一個字段在任何情況下都被 json 包忽略,需要使用如下的寫法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
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:"-" 標簽的字段都被忽略了。
補充:golang string轉json的一些坑
先看一段代碼,起作用是把字符串轉換為結構體對應的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
37
38
39
40
41
|
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) } } |
仔細看看,有沒有錯?我只能說,這樣是輸出不出來答案的,賦值錯誤,看下面的運行結果:
傷腦筋啊,我仔細看了半天,發現在定義的people和student兩個結構體下邊有綠色的波浪線(我用的vscode),像下邊這樣:
鼠標放上去顯示的是:
大家都知道,golang中變量聲明成大寫和小寫能引用的范圍是不一樣的,那我就想了,大小寫問題???一臉懵逼把變量名首字母改成了大寫,然后...就行了,代碼變成了下邊這樣:
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
|
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) } } |
輸出的結果這樣:
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:https://www.jianshu.com/p/2b4a3cda0f6f