go json轉換實踐中遇到的坑

在使用 go 語言開發過程中,經常需要使用到 json 包來進行 json 和 struct 的互相轉換,這篇文章主要介紹了go json轉換實踐中遇到的坑,非常具有實用價值,需要的朋友可以參考下

在使用 go 語言開發過程中,經常需要使用到 json 包來進行 json 和 struct 的互相轉換,在使用過程中,遇到了一些需要額外注意的地方,記錄如下。

整數變浮點數問題

假設有一個 Person 結構,其中包含 Age int64 和 Weight float64 兩個字段,現在通過 json 包將 Person 結構轉爲 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{})
}

代碼執行到這裏看上去一切正常,但是打印一下 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 類型,這顯然是不符合我們的預期的。

查看 json 的規範可以看到,在 json 中是沒有整型和浮點型之分的,所以現在可以理解 json 包中的 Unmarshal 方法轉出的數字類型爲什麼都是 float64 了,因爲根據 json 規範,數字都是同一種類型,那麼對應到 go 的類型中最接近的就是 float64 了。

json 包還針對這個問題提供了更好的解決方案,不過需要使用 json.Decoder 來代替 json.Unmarshal 方法,將 json.Unmarhsal 替換如下。

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 類型轉換爲字符串後的格式。

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:"-" 標籤的字段都被忽略了。

補充:golang string轉json的一些坑

先看一段代碼,起作用是把字符串轉換爲結構體對應的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)

 }

}

仔細看看,有沒有錯?我只能說,這樣是輸出不出來答案的,賦值錯誤,看下面的運行結果:

傷腦筋啊,我仔細看了半天,發現在定義的people和student兩個結構體下邊有綠色的波浪線(我用的vscode),像下邊這樣:

鼠標放上去顯示的是:

大家都知道,golang中變量聲明成大寫和小寫能引用的範圍是不一樣的,那我就想了,大小寫問題???一臉懵逼把變量名首字母改成了大寫,然後...就行了,代碼變成了下邊這樣:

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)

 }

} 

輸出的結果這樣:

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持神馬文庫。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章