在使用 go 語言開發過程當中,常常須要使用到 json 包來進行 json 和 struct 的互相轉換,在使用過程當中,遇到了一些須要額外注意的地方,記錄以下。golang
假設有一個 Person 結構,其中包含 Age int64 和 Weight float64 兩個字段,如今經過 json 包將 Person 結構轉爲 map[string]interface{},代碼以下。json
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{} 就會發現不太對了。測試
fmt.Println(reflect.TypeOf(r["Age"]).Name()) // float64
fmt.Println(reflect.TypeOf(r["Weight"]).Name()) // float64
複製代碼
轉換成 map[string]interface{} 以後,原先的 uint64 和 float64 類型都被轉換成了 float64 類型,這顯然是不符合咱們的預期的。ui
查看 json 的規範能夠看到,在 json 中是沒有整型和浮點型之分的,因此如今能夠理解 json 包中的 Unmarshal 方法轉出的數字類型爲何都是 float64 了,由於根據 json 規範,數字都是同一種類型,那麼對應到 go 的類型中最接近的就是 float64 了。spa
json 包還針對這個問題提供了更好的解決方案,不過須要使用 json.Decoder 來代替 json.Unmarshal 方法,將 json.Unmarhsal 替換以下。code
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 類型提供了轉換爲 int6四、float64 等多個方法。cdn
對於 json 格式,是沒有時間類型的,日期和時間以 json 格式存儲時,須要轉換爲字符串類型。這就帶來了一個問題,日期時間的字符串表示有多種多樣,go 的 json 包支持的是哪種呢?對象
使用下面的代碼來輸出 json.Marshal 方法將 Time 類型轉換爲字符串後的格式。blog
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 方法所支持的日期時間格式。開發
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 包對於空值的處理是一個很是容易出錯的地方,看下面代碼。
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)時,忽略該字段。
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 包忽略,須要使用以下的寫法。
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:"-" 標籤的字段都被忽略了。