## 使用指針和SQL Null類型進行編碼和解碼
當您對Go中的對象進行編碼或解碼時,未顯式設置的類型將被填充為其默認值。字符串將默認為空字符串,整數將默認為0。通常這沒什么問題,但當0在業務上包含其他含義時,歧義就出現了。
此外,如果使用結構標記,例如json omitempty,即使它們有效,也會忽略0值。 另一個例子是從SQL返回的Null。 對于Int來說,什么值最能代表Null?本文將探討Go開發人員處理此問題的一些方法。
### 實踐
1. 建立 base.go:
```
package nulls
import (
"encoding/json"
"fmt"
)
const (
jsonBlob = `{"name": "Aaron"}`
fulljsonBlob = `{"name":"Aaron", "age":0}`
)
// Example結構體包含age和name字段
type Example struct {
Age int `json:"age,omitempty"`
Name string `json:"name"`
}
// BaseEncoding 演示了基本的編碼和解碼操作
func BaseEncoding() error {
e := Example{}
// 注意jsonBlob沒有age字段
if err := json.Unmarshal([]byte(jsonBlob), &e); err != nil {
return err
}
fmt.Printf("Regular Unmarshal, no age: %+v\n", e)
value, err := json.Marshal(&e)
if err != nil {
return err
}
//由于age被設置為omitempty(為空則不輸出) 所以顯示 Regular Marshal, with no age: {"name":"Aaron"}
fmt.Println("Regular Marshal, with no age:", string(value))
if err := json.Unmarshal([]byte(fulljsonBlob), &e); err != nil {
return err
}
fmt.Printf("Regular Unmarshal, with age = 0: %+v\n", e)
value, err = json.Marshal(&e)
if err != nil {
return err
}
//Regular Marshal, with age = 0: {"name":"Aaron"}
fmt.Println("Regular Marshal, with age = 0:", string(value))
return nil
}
```
2. 建立pointer.go:
```
package nulls
import (
"encoding/json"
"fmt"
)
// 和上一個例子類似 但是*int類型會出現奇妙的nil
// uses a *Int
type ExamplePointer struct {
Age *int `json:"age,omitempty"`
Name string `json:"name"`
}
func PointerEncoding() error {
e := ExamplePointer{}
if err := json.Unmarshal([]byte(jsonBlob), &e); err != nil {
return err
}
//Pointer Unmarshal, no age: {Age:<nil> Name:Aaron}
fmt.Printf("Pointer Unmarshal, no age: %+v\n", e)
value, err := json.Marshal(&e)
if err != nil {
return err
}
//Pointer Marshal, with no age: {"name":"Aaron"}
fmt.Println("Pointer Marshal, with no age:", string(value))
if err := json.Unmarshal([]byte(fulljsonBlob), &e); err != nil {
return err
}
//Pointer Unmarshal, with age = 0: {Age:0xc04200e4b8 Name:Aaron}
fmt.Printf("Pointer Unmarshal, with age = 0: %+v\n", e)
value, err = json.Marshal(&e)
if err != nil {
return err
}
//Pointer Marshal, with age = 0: {"age":0,"name":"Aaron"}
fmt.Println("Pointer Marshal, with age = 0:", string(value))
return nil
}
```
3. 建立nullencoding.go:
```
package nulls
import (
"database/sql"
"encoding/json"
"fmt"
)
type nullInt64 sql.NullInt64
// 和前面的例子類似 又改變了Age的類型sql.NullInt64
type ExampleNullInt struct {
Age *nullInt64 `json:"age,omitempty"`
Name string `json:"name"`
}
func (v *nullInt64) MarshalJSON() ([]byte, error) {
if v.Valid {
return json.Marshal(v.Int64)
}
return json.Marshal(nil)
}
func (v *nullInt64) UnmarshalJSON(b []byte) error {
v.Valid = false
if b != nil {
v.Valid = true
return json.Unmarshal(b, &v.Int64)
}
return nil
}
func NullEncoding() error {
e := ExampleNullInt{}
if err := json.Unmarshal([]byte(jsonBlob), &e); err != nil {
return err
}
//nullInt64 Unmarshal, no age: {Age:<nil> Name:Aaron}
fmt.Printf("nullInt64 Unmarshal, no age: %+v\n", e)
value, err := json.Marshal(&e)
if err != nil {
return err
}
//nullInt64 Marshal, with no age: {"name":"Aaron"}
fmt.Println("nullInt64 Marshal, with no age:", string(value))
if err := json.Unmarshal([]byte(fulljsonBlob), &e); err != nil {
return err
}
//nullInt64 Unmarshal, with age = 0: {Age:0xc0420623f0 Name:Aaron}
fmt.Printf("nullInt64 Unmarshal, with age = 0: %+v\n", e)
value, err = json.Marshal(&e)
if err != nil {
return err
}
//nullInt64 Marshal, with age = 0: {"age":0,"name":"Aaron"}
fmt.Println("nullInt64 Marshal, with age = 0:", string(value))
return nil
}
```
4. 建立main.go:
```
package main
import (
"fmt"
"github.com/agtorre/go-cookbook/chapter3/nulls"
)
func main() {
if err := nulls.BaseEncoding(); err != nil {
panic(err)
}
fmt.Println()
if err := nulls.PointerEncoding(); err != nil {
panic(err)
}
fmt.Println()
if err := nulls.NullEncoding(); err != nil {
panic(err)
}
}
```
5. 這會輸出:
```
Regular Unmarshal, no age: {Age:0 Name:Aaron}
Regular Marshal, with no age: {"name":"Aaron"}
Regular Unmarshal, with age = 0: {Age:0 Name:Aaron}
Regular Marshal, with age = 0: {"name":"Aaron"}
Pointer Unmarshal, no age: {Age:<nil> Name:Aaron}
Pointer Marshal, with no age: {"name":"Aaron"}
Pointer Unmarshal, with age = 0: {Age:0xc42000a610 Name:Aaron}
Pointer Marshal, with age = 0: {"age":0,"name":"Aaron"}
nullInt64 Unmarshal, no age: {Age:<nil> Name:Aaron}
nullInt64 Marshal, with no age: {"name":"Aaron"}
nullInt64 Unmarshal, with age = 0: {Age:0xc42000a750 Name:Aaron}
nullInt64 Marshal, with age = 0: {"age":0,"name":"Aaron"}
```
### 說明
從值切換到指針是表示空值的快速方法。這可能會為初始化帶來點麻煩,因為無法直接操作*a := 1。除此之外這是一種不錯的處理方式。
本節還演示了使用sql.NullInt64類型的替代方法。這通常與SQL一起使用,如果返回Null以外的任何內容,則設置valid,否則設置為Null。我們添加了MarshalJSON和UnmarshallJSON方法以允許此類型與JSON包交互,我們選擇使用指針,以便omitempty將繼續按預期工作。
* * * *
學識淺薄,錯誤在所難免。歡迎在群中就本書提出修改意見,以饗后來者,長風拜謝。
Golang中國(211938256)
beego實戰(258969317)
Go實踐(386056972)
- 前言
- 第一章 I/O和文件系統
- 常見 I/O 接口
- 使用bytes和strings包
- 操作文件夾和文件
- 使用CSV格式化數據
- 操作臨時文件
- 使用 text/template和HTML/templates包
- 第二章 命令行工具
- 解析命令行flag標識
- 解析命令行參數
- 讀取和設置環境變量
- 操作TOML,YAML和JSON配置文件
- 操做Unix系統下的pipe管道
- 處理信號量
- ANSI命令行著色
- 第三章 數據類型轉換和解析
- 數據類型和接口轉換
- 使用math包和math/big包處理數字類型
- 貨幣轉換和float64注意事項
- 使用指針和SQL Null類型進行編碼和解碼
- 對Go數據編碼和解碼
- Go中的結構體標簽和反射
- 通過閉包實現集合操作
- 第四章 錯誤處理
- 錯誤接口
- 使用第三方errors包
- 使用log包記錄錯誤
- 結構化日志記錄
- 使用context包進行日志記錄
- 使用包級全局變量
- 處理恐慌
- 第五章 數據存儲
- 使用database/sql包操作MySQL
- 執行數據庫事務接口
- SQL的連接池速率限制和超時
- 操作Redis
- 操作MongoDB
- 創建存儲接口以實現數據可移植性
- 第六章 Web客戶端和APIs
- 使用http.Client
- 調用REST API
- 并發操作客戶端請求
- 使用OAuth2
- 實現OAuth2令牌存儲接口
- 封裝http請求客戶端
- 理解GRPC的使用
- 第七章 網絡服務
- 處理Web請求
- 使用閉包進行狀態處理
- 請求參數驗證
- 內容渲染
- 使用中間件
- 構建反向代理
- 將GRPC導出為JSON API
- 第八章 測試
- 使用標準庫進行模擬
- 使用Mockgen包
- 使用表驅動測試
- 使用第三方測試工具
- 模糊測試
- 行為驅動測試
- 第九章 并發和并行
- 第十章 分布式系統
- 第十一章 響應式編程和數據流
- 第十二章 無服務器編程
- 第十三章 性能改進