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)