SQLのSELECTの結果がNULLを返し、それを何らかのデータに格納する場合、格納先のデータの型を特別なものに指定する必要があります。
NULLを返すSQLを使用する場合、次のコードはエラーになります。
type someModel struct {
code int
name string
}
func main() {
rets := []*model.MasterCategory{}
... // NULLを返すSQLを生成
for rows.Next() {
sm := &someModel{}
err := rows.Scan(
&sm.code,
&sm.name,
)
...
}
...
}
converting driver.Value type
SQLがNULLを返す場合、sql.NullXXXという型を指定します。
type someModel struct {
code sql.NullInt64
name sql.NullString
}
sql.NullStringやsql.NullInt64は「sql.NullXXX」の「XXX」を型にもつ値のフィールドと、その値がnullかどうかを判定するbool型(NULLでない場合にtrue)を持つValidフィールドから構成される構造体で、次のように定義されています。
type NullInt64 struct {
Int64 int64
Valid bool // Int64 がNULLでない場合はtrue
}
type NullString struct {
String string
Valid bool // String がNULLでない場合はtrue
}
なお、timeの場合は、mysqlパッケージを使って、次のように定義します。
type timeSample struct {
start mysql.NullTime
finish mysql.NullTime
}
これでエラーは解消されますが、JSON形式で出力しようとすると、sql.NullXXXを指定したフィールドは、{Int64: 1, Valid: true}のように構造体の形のまま出力されてしまいます。
そこで、JSONのMarshal、Unmarshalメソッドを再定義します。ただし、sql.NullXXXのメソッドを直接拡張することは許されていないため、新しくsql.NullXXXを参照する型を定義する必要があります。
type NullInt64 struct { // 新たに型を定義
sql.NullInt64
}
type NullString struct { // 新たに型を定義
sql.NullString
}
type someModel struct {
code NullInt64 // 新しい型を指定
name NullString // 新しい型を指定
}
func (ni *NullInt64) UnmarshalJSON(value []byte) error {
err := json.Unmarshal(value, ni.Int64)
ni.Valid = err == nil
return err
}
func (ni NullInt64) MarshalJSON() ([]byte, error) {
if !ni.Valid {
return json.Marshal(nil)
}
return json.Marshal(ni.Int64) // 値のフィールドのみ返す
}
func (ns *NullString) UnmarshalJSON(value []byte) error {
err := json.Unmarshal(value, ns.String)
ns.Valid = err == nil
return err
}
func (ns NullString) MarshalJSON() ([]byte, error) {
if !ns.Valid {
return json.Marshal(nil)
}
return json.Marshal(ns.String) // 値のフィールドのみ返す
}
これはあくまでJSONの扱いに関するもので、データとしての型は構造体のままとなります。
How I handled possible null values from database rows in Golang?
nils In Go - Go 101 (Golang Knowledgebase)