技術向上

プログラミングの学び、気になるテクノロジーやビジネストレンドを発信

SQLのNULL値に対応する(JSON定義の変更まで)【Go】

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 ("") to a intというようなエラーとなります。nil型のものは型の異なるintやstringには変換できないということです。

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?

Golangのnilについて - Carpe Diem

nils In Go - Go 101 (Golang Knowledgebase)