淺析golang如何處理json中的null
最近學(xué)習(xí) go 發(fā)現(xiàn)發(fā)現(xiàn)處理 json 中的 null 時,會這么難受,需要專門寫一篇文章來講解一下
以下是正文
json 是一種常用的數(shù)據(jù)格式,在 go 使用 json 序列化和反序列化時比較方便的,但在使用過程中,會遇到一些問題,比如 null
由于 go 沒有聯(lián)合類型,當 json 中有個屬性為 null 時,就無法直接將 null 轉(zhuǎn)換成 nil 后賦值給某個具體的類型
比如下面這個例子:
Name 定一個的是 string 類型,但在 json 中 name 的值為 null,直接轉(zhuǎn)換會報錯
type Tag struct {
ID int `json:"id"`
Name string `json:"name"`
}
tag := Tag{
ID: 1,
Name: nil, // 這里會報錯
}這種問題不光出現(xiàn)在 json 解析時,還會出現(xiàn)在數(shù)據(jù)庫讀寫時
比如在數(shù)據(jù)庫中,某個字段的值為 NULL,在讀取時,會被解析成 nil,但是 go 中的類型是不能直接賦值為 nil 的
所以在這兩種場景下該怎么解決呢?
一般有三種方法:
- 使用指針
- 自定義類型
- 使用第三方庫
使用指針
go 的指針類型是可以賦值為 nil 的,所以我們使用指針解決這個問題
我們把上面例子中的 Name 定義為 string 的指針類型,如下代碼:
type Tag struct {
ID int `json:"id"`
Name *string `json:"name"`
}
name := "uccs" // 定義一個 string 類型的變量,因為不能把一個字面量直接賦值給指針類型
tag := Tag{
ID: 1,
Name: &name, // 將 name 的地址賦值給 Name,使用 & 地址符
}在使用時,需要先判斷一下 Name 是為 nil,如果不為 nil,則使用 * 取值符取出值
// Name 是指針類型,判斷是否為 nil 時不需要使用 * 取值符
if tag.Name != nil {
// Name 是指針類型,取值時需要使用 * 取值符
if *tag.Name == "uccs" {
// ...
}
}注意事項
ORM 框架會實現(xiàn)一個 NullString 的類型,
當我們在定義 Model 時,如果某個字段可以為 NULL,則 ORM 框架會把它定義為 NullString 類型(下文講解)
給指針賦值時,不能直接使用字面量,需要先定義一個變量,然后將變量的地址賦值給指針
使用指針時需要注意,這里會比較繞
在判斷是否為 nil 時,不需要使用 * 取值符
在判斷是否為 uccs 時,需要使用 * 取值符
當遇到 panic: runtime error: invalid memory address or nil pointer dereference 錯誤時,說明指針為 nil
也就是說使用指針時,我們最需要注意的是:在指針上取值時,一定要注意它是不是為 nil
自定義類型
我們使用結(jié)構(gòu)體定義一個類型:NullString,它有兩個屬性 String 和 Valid
String 用來存儲字符串
Valid 用來標識 String 是否有值
- 如果
Valid為true,則String有值 - 如果
Valid為false,則String是空值""
type NullString struct {
String string
Valid bool
}當我們定義好類型后,需要考考慮兩個問題:
- 如何解決
json解析時null的問題 - 如何向數(shù)據(jù)庫進行讀寫
go 有個特點,你自定義的類型有某些方法,那么在某些場景下,這些方法會被調(diào)用
比如,序列化時,會調(diào)用 MarshalJSON 方法,反序列化時,會調(diào)用 UnmarshalJSON 方法
你的自定義類型實現(xiàn)了這兩個方法,那么在序列化和反序列化時,這兩個方法就會被調(diào)用
數(shù)據(jù)庫讀寫是實現(xiàn) Scan 和 Value 方法
所以下面就從這兩塊講起:
序列化和反序列化
我們給 NullString 類型添加兩個方法 MarshalJSON 和 UnmarshalJSON
// 序列化時
func (ns NullString) MarshalJSON() ([]byte, error) {
// 如果 Valid 為 true,則返回 String 的 json 序列化結(jié)果
if ns.Valid {
return []byte(`"` + ns.String + `"`), nil
}
// 如果 Valid 為 false,則返回 null 序列化的結(jié)果
return []byte("null"), nil
}
// 反序列化
func (ns *NullString) UnmarshalJSON(data []byte) error {
// 如果 data 為 null,則 Valid 為 false
// String 為空字符串
if string(data) == "null" {
ns.String, ns.Valid = "", false
return nil
}
// 否則,將 data 反序列化到 String 中
// 并將 Valid 設(shè)置為 true
if err := json.Unmarshal(data, &ns.String); err != nil {
return err
}
ns.Valid = true
return nil
}有了這兩個方法之后,我們就解決了 json 解析時 null 的問題
是什么時候會觸發(fā)這兩個方法呢?
從 json 內(nèi)容解析填充 struct 的場景時會觸發(fā) UnmarshalJSON 的調(diào)用
- 直接調(diào)用
json.Unmarshal對json數(shù)據(jù)進行解析時 http.Request讀取json Body時- 使用
encoding/json的Decoder進行解碼時 - 對實現(xiàn)了
Unmarshaler接口的對象調(diào)用UnmarshalJSON方法時
反過來,將 struct 內(nèi)容序列化為 json 時會觸發(fā) json.Marshal 的調(diào)用
- 直接調(diào)用
json.Marshal對一個對象進行編碼 - 使用
http.ResponseWriter的Write方法響應(yīng)json數(shù)據(jù)時 - 使用
encoding/json的Encoder進行編碼時 - 對實現(xiàn)了
Marshaler接口的對象調(diào)用MarshalJSON方法時
序列化和反序列化問題解決了,那如何向數(shù)據(jù)庫進行讀寫呢?
數(shù)據(jù)庫讀寫
我們再給 NullString 添加兩個方法 Value 和 Scan
Value方法會在寫入數(shù)據(jù)庫時被調(diào)用Scan方法會在從數(shù)據(jù)庫讀取時被調(diào)用
// Scan 方法在 數(shù)據(jù)庫讀取時被調(diào)用
func (ns *NullString) Scan(value interface{}) error {
// 如果 value 為 nil,則 Valid 為 false,String 為空字符串
if value == nil {
ns.String, ns.Valid = "", false
return nil
}
// 否則,將 value 斷言為 string 類型,斷言成功 Valid 為 true,String 為 value
ns.String, ns.Valid = value.(string)
return nil
}
// Value 方法 在寫入數(shù)據(jù)庫時被調(diào)用
func (ns NullString) Value() (driver.Value, error) {
// 如果 Valid 為 false,則返回 nil
if !ns.Valid {
return nil, nil
}
// 否則,返回 String
return ns.String, nil
}添加這兩個方法后,我們就可以向數(shù)據(jù)庫中寫入 null 了
是什么時候會觸發(fā)這兩個方法呢?
Scanner 接口的 Scan 方法會在以下情況被調(diào)用
ORM 框架如 GORM、database/sql 等查詢時,掃描結(jié)果到自定義模型
Valuer 接口的 Value 方法會在以下情況被調(diào)用
ORM 框架如 GORM、database/sql 構(gòu)造寫入語句時,獲取自定義模型的值
使用
將上面 Tag 的解構(gòu)體改為:
type Tag struct {
ID int `json:"id"`
Name NullString `json:"name"`
}不過這里要注意的一點是,在給 Name 賦值時,需要使用 NullString 進行賦值,如果下所示:
tag := Tag{
ID: 1,
Name: NullString{String: "hello", Valid: true},
}最后需要注意的是,go 中其他類型也要實現(xiàn)這樣的方法,比如 NullInt,NullBool 等,可以參照這個 guregu/null 這個庫
使用第三方庫
第三方庫 guregu/null 已經(jīng)實現(xiàn)了上面的方法,我們可以直接使用
ORM 一般都實現(xiàn)了這些功能
需要注意的是有些 ORM 只實現(xiàn)了 Scanner 和 Valuer 接口,沒有實現(xiàn) MarshalJSON 和 UnmarshalJSON 接口
總結(jié)
- 使用
string只能滿足必填的情況 ORM框架一般都實現(xiàn)了Scanner和Valuer接口,但是有些ORM沒有實現(xiàn)MarshalJSON和UnmarshalJSON接口,需要自己實現(xiàn),或者使用第三方庫- 使用指針時,如
*string,需要注意指針是否為nil
到此這篇關(guān)于淺析golang如何處理json中的null的文章就介紹到這了,更多相關(guān)go處理json內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

