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