├── .travis.yml ├── README.md ├── _example └── main.go ├── bool.go ├── bool_test.go ├── example_fmt_stringer_test.go ├── example_json_marshaler_test.go ├── example_json_unmarshaler_test.go ├── float64.go ├── float64_test.go ├── go.mod ├── go.test.sh ├── int64.go ├── int64_test.go ├── nulltype.go ├── nulltype_test.go ├── string.go ├── string_test.go ├── time.go └── time_test.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | sudo: false 3 | go: 4 | - 1.8.x 5 | - tip 6 | 7 | before_install: 8 | - go get -t -v ./... 9 | 10 | script: 11 | - ./go.test.sh 12 | 13 | after_success: 14 | - bash <(curl -s https://codecov.io/bash) 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-nulltype 2 | 3 | [![Build Status](https://travis-ci.org/mattn/go-nulltype.svg?branch=master)](https://travis-ci.org/mattn/go-nulltype) 4 | [![codecov](https://codecov.io/gh/mattn/go-nulltype/branch/master/graph/badge.svg)](https://codecov.io/gh/mattn/go-nulltype) 5 | 6 | Nullable types friendly to json.Encoder, json.Decoder, database/sql, fmt.Stringer, text/template, html/template, some of ORMs. 7 | 8 | Supported types: 9 | 10 | * NullBool 11 | * NullString 12 | * NullFloat64 13 | * NullInt64 14 | * NullTime 15 | 16 | ## Usage 17 | 18 | ```go 19 | import "github.com/mattn/go-nulltype" 20 | 21 | type User struct { 22 | Name nulltype.NullString `json:"name"` 23 | } 24 | ``` 25 | 26 | ### friendly to Stringer 27 | 28 | ```go 29 | var user User 30 | fmt.Println(user.Name.Valid()) // false 31 | fmt.Println(user.Name) // "" 32 | 33 | user.Name.Set("Bob") 34 | fmt.Println(user.Name.Valid()) // true 35 | fmt.Println(user.Name) // "Bob" 36 | 37 | fmt.Println(user.Name.StringValue() == "Bob") // true 38 | 39 | user.Name.Reset() 40 | fmt.Println(user.Name.Valid()) // false 41 | fmt.Println(user.Name) // "" 42 | ``` 43 | 44 | ### friendly to json.MarshalJSON 45 | 46 | ```go 47 | var user User 48 | fmt.Println(user.Name.Valid()) // false 49 | json.NewEncoder(os.Stdout).Encode(user) // {"name": null} 50 | 51 | user.Name.Set("Bob") 52 | fmt.Println(user.Name.Valid()) // true 53 | json.NewEncoder(os.Stdout).Encode(user) // {"name": "Bob"} 54 | ``` 55 | 56 | ### friendly to json.UnmarshalJSON 57 | 58 | ```go 59 | var user User 60 | s := `{"name": "Bob"}` 61 | json.NewDecoder(strings.NewReader(s)).Decode(&user) 62 | fmt.Println(user.Name.Valid()) // true 63 | fmt.Println(user.Name) // "Bob" 64 | 65 | s = `{"name": null}` 66 | json.NewDecoder(strings.NewReader(s)).Decode(&user) 67 | fmt.Println(user.Name.Valid()) // false 68 | fmt.Println(user.Name) // "" 69 | ``` 70 | 71 | ### friendly to database/sql 72 | 73 | ```go 74 | var user User 75 | db.QueryRow(`SELECT name FROM users`).Scan(&user.Name) 76 | fmt.Println(user.Name.Valid()) // true or false 77 | fmt.Println(user.Name) // "Bob" or "" 78 | db.Exec(`INSERT INTO users(name) VALUES($1)`, user.Name) 79 | ``` 80 | 81 | ### friendly to ORM 82 | 83 | Struct tag with [gorp](https://github.com/go-gorp/gorp). 84 | 85 | ```go 86 | type Post struct { 87 | Id int64 `db:"post_id"` 88 | Created int64 89 | Title string `db:",size:50"` 90 | Body nulltype.NullString `db:"body,size:1024"` 91 | } 92 | ``` 93 | 94 | ```go 95 | p := Post{ 96 | Created: time.Now().UnixNano(), 97 | Title: title, 98 | Body: nulltype.NullStringOf(body), 99 | } 100 | err = dbmap.Insert(&p) 101 | ``` 102 | 103 | ## Installation 104 | 105 | ``` 106 | go get github.com/mattn/go-nulltype 107 | ``` 108 | 109 | ## License 110 | 111 | MIT 112 | 113 | ## Author 114 | 115 | Yasuhiro Matsumoto 116 | -------------------------------------------------------------------------------- /_example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "log" 7 | 8 | "github.com/mattn/go-nulltype" 9 | _ "github.com/mattn/go-sqlite3" 10 | ) 11 | 12 | func main() { 13 | db, err := sql.Open("sqlite3", ":memory:") 14 | if err != nil { 15 | log.Fatal(err) 16 | } 17 | defer db.Close() 18 | 19 | var nt nulltype.NullTime 20 | err = db.QueryRow("select current_timestamp").Scan(&nt) 21 | if err != nil { 22 | log.Fatal(err) 23 | } 24 | fmt.Println(nt) 25 | } 26 | -------------------------------------------------------------------------------- /bool.go: -------------------------------------------------------------------------------- 1 | package nulltype 2 | 3 | import ( 4 | "database/sql" 5 | "database/sql/driver" 6 | "encoding/json" 7 | ) 8 | 9 | // NullBool is null friendly type for bool. 10 | type NullBool struct { 11 | b sql.NullBool 12 | } 13 | 14 | // NullBoolOf return NullBool that the value is set. 15 | func NullBoolOf(value bool) NullBool { 16 | var b NullBool 17 | b.Set(value) 18 | return b 19 | } 20 | 21 | // Valid return the value is valid. If true, it is not null value. 22 | func (b *NullBool) Valid() bool { 23 | return b.b.Valid 24 | } 25 | 26 | // BoolValue return the value. 27 | func (b *NullBool) BoolValue() bool { 28 | return b.b.Bool 29 | } 30 | 31 | // Reset set nil to the value. 32 | func (b *NullBool) Reset() { 33 | b.b.Bool = false 34 | b.b.Valid = false 35 | } 36 | 37 | // Set set the value. 38 | func (b *NullBool) Set(value bool) *NullBool { 39 | b.b.Valid = true 40 | b.b.Bool = value 41 | return b 42 | } 43 | 44 | // Scan is a method for database/sql. 45 | func (b *NullBool) Scan(value interface{}) error { 46 | return b.b.Scan(value) 47 | } 48 | 49 | // String return string indicated the value. 50 | func (b NullBool) String() string { 51 | if !b.b.Valid { 52 | return "" 53 | } 54 | if b.b.Bool { 55 | return "true" 56 | } 57 | return "false" 58 | } 59 | 60 | // MarshalJSON encode the value to JSON. 61 | func (b NullBool) MarshalJSON() ([]byte, error) { 62 | if !b.b.Valid { 63 | return []byte("null"), nil 64 | } 65 | return json.Marshal(b.b.Bool) 66 | } 67 | 68 | // UnmarshalJSON decode data to the value. 69 | func (b *NullBool) UnmarshalJSON(data []byte) error { 70 | var value *bool 71 | if err := json.Unmarshal(data, &value); err != nil { 72 | return err 73 | } 74 | b.b.Valid = value != nil 75 | if value == nil { 76 | b.b.Bool = false 77 | } else { 78 | b.b.Bool = *value 79 | } 80 | return nil 81 | } 82 | 83 | // Value implement driver.Valuer. 84 | func (b NullBool) Value() (driver.Value, error) { 85 | if !b.Valid() { 86 | return nil, nil 87 | } 88 | return b.b.Bool, nil 89 | } 90 | -------------------------------------------------------------------------------- /bool_test.go: -------------------------------------------------------------------------------- 1 | package nulltype 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "strings" 8 | "testing" 9 | ) 10 | 11 | func TestNullBoolStringer(t *testing.T) { 12 | var b NullBool 13 | 14 | want := "" 15 | got := fmt.Sprint(b) 16 | if got != want { 17 | t.Fatalf("want %v, but %v:", want, got) 18 | } 19 | 20 | want = "true" 21 | b.Set(true) 22 | got = fmt.Sprint(b) 23 | if got != want { 24 | t.Fatalf("want %v, but %v:", want, got) 25 | } 26 | 27 | want = "false" 28 | b = NullBoolOf(false) 29 | got = fmt.Sprint(b) 30 | if got != want { 31 | t.Fatalf("want %v, but %v:", want, got) 32 | } 33 | 34 | want = "" 35 | b.Reset() 36 | got = fmt.Sprint(b) 37 | if got != want { 38 | t.Fatalf("want %v, but %v:", want, got) 39 | } 40 | } 41 | 42 | func TestNullBoolMarshalJSON(t *testing.T) { 43 | var b NullBool 44 | 45 | var buf bytes.Buffer 46 | err := json.NewEncoder(&buf).Encode(b) 47 | if err != nil { 48 | t.Fatal(err) 49 | } 50 | 51 | want := "null" 52 | got := strings.TrimSpace(buf.String()) 53 | if got != want { 54 | t.Fatalf("want %v, but %v:", want, got) 55 | } 56 | 57 | buf.Reset() 58 | 59 | b.Set(true) 60 | err = json.NewEncoder(&buf).Encode(b) 61 | if err != nil { 62 | t.Fatal(err) 63 | } 64 | 65 | want = "true" 66 | got = strings.TrimSpace(buf.String()) 67 | if got != want { 68 | t.Fatalf("want %v, but %v:", want, got) 69 | } 70 | 71 | buf.Reset() 72 | 73 | b.Set(false) 74 | err = json.NewEncoder(&buf).Encode(b) 75 | if err != nil { 76 | t.Fatal(err) 77 | } 78 | 79 | want = "false" 80 | got = strings.TrimSpace(buf.String()) 81 | if got != want { 82 | t.Fatalf("want %v, but %v:", want, got) 83 | } 84 | } 85 | 86 | func TestNullBoolUnmarshalJSON(t *testing.T) { 87 | var b NullBool 88 | 89 | err := json.NewDecoder(strings.NewReader("null")).Decode(&b) 90 | if err != nil { 91 | t.Fatal(err) 92 | } 93 | 94 | if b.Valid() { 95 | t.Fatalf("must be null but got %v", b) 96 | } 97 | 98 | err = json.NewDecoder(strings.NewReader("true")).Decode(&b) 99 | if err != nil { 100 | t.Fatal(err) 101 | } 102 | 103 | if !b.Valid() { 104 | t.Fatalf("must not be null but got nil") 105 | } 106 | 107 | want := true 108 | got := b.BoolValue() 109 | if got != want { 110 | t.Fatalf("want %v, but %v:", want, got) 111 | } 112 | 113 | err = json.NewDecoder(strings.NewReader("false")).Decode(&b) 114 | if err != nil { 115 | t.Fatal(err) 116 | } 117 | 118 | if !b.Valid() { 119 | t.Fatalf("must not be null but got nil") 120 | } 121 | 122 | want = false 123 | got = b.BoolValue() 124 | if got != want { 125 | t.Fatalf("want %v, but %v:", want, got) 126 | } 127 | 128 | err = json.NewDecoder(strings.NewReader(`"foo"`)).Decode(&b) 129 | if err == nil { 130 | t.Fatal("should be fail") 131 | } 132 | } 133 | 134 | func TestNullBoolValueConverter(t *testing.T) { 135 | var b NullBool 136 | 137 | err := b.Scan("1") 138 | if err != nil { 139 | t.Fatal(err) 140 | } 141 | 142 | if !b.Valid() { 143 | t.Fatalf("must not be null but got nil") 144 | } 145 | 146 | want := true 147 | got := b.BoolValue() 148 | if got != want { 149 | t.Fatalf("want %v, but %v:", want, got) 150 | } 151 | 152 | gotv, err := b.Value() 153 | if err != nil { 154 | t.Fatal(err) 155 | } 156 | if gotv != want { 157 | t.Fatalf("want %v, but %v:", want, got) 158 | } 159 | 160 | b.Reset() 161 | 162 | gotv, err = b.Value() 163 | if err != nil { 164 | t.Fatal(err) 165 | } 166 | if gotv != nil { 167 | t.Fatalf("must be null but got %v", gotv) 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /example_fmt_stringer_test.go: -------------------------------------------------------------------------------- 1 | package nulltype_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/mattn/go-nulltype" 7 | ) 8 | 9 | type User struct { 10 | Name nulltype.NullString `json:"name"` 11 | } 12 | 13 | func Example_fmtStringer() { 14 | var user User 15 | fmt.Printf("%v, %q\n", user.Name.Valid(), user.Name) 16 | 17 | user.Name.Set("Bob") 18 | fmt.Printf("%v, %q\n", user.Name.Valid(), user.Name) 19 | 20 | fmt.Println(user.Name.StringValue() == "Bob") // true 21 | 22 | user.Name.Reset() 23 | fmt.Printf("%v, %q\n", user.Name.Valid(), user.Name) 24 | 25 | // Output: 26 | // false, "" 27 | // true, "Bob" 28 | // true 29 | // false, "" 30 | } 31 | -------------------------------------------------------------------------------- /example_json_marshaler_test.go: -------------------------------------------------------------------------------- 1 | package nulltype_test 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | func Example_jsonMarshaler() { 10 | var user User 11 | fmt.Println(user.Name.Valid()) 12 | json.NewEncoder(os.Stdout).Encode(user) 13 | 14 | user.Name.Set("Bob") 15 | fmt.Println(user.Name.Valid()) 16 | json.NewEncoder(os.Stdout).Encode(user) 17 | 18 | // Output: 19 | // false 20 | // {"name":null} 21 | // true 22 | // {"name":"Bob"} 23 | } 24 | -------------------------------------------------------------------------------- /example_json_unmarshaler_test.go: -------------------------------------------------------------------------------- 1 | package nulltype_test 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "strings" 7 | ) 8 | 9 | func Example_jsonUnmarshaler() { 10 | var user User 11 | s := `{"name": "Bob"}` 12 | json.NewDecoder(strings.NewReader(s)).Decode(&user) 13 | fmt.Printf("%v, %q\n", user.Name.Valid(), user.Name) 14 | 15 | s = `{"name": null}` 16 | json.NewDecoder(strings.NewReader(s)).Decode(&user) 17 | fmt.Printf("%v, %q\n", user.Name.Valid(), user.Name) 18 | 19 | // Output: 20 | // true, "Bob" 21 | // false, "" 22 | } 23 | -------------------------------------------------------------------------------- /float64.go: -------------------------------------------------------------------------------- 1 | package nulltype 2 | 3 | import ( 4 | "database/sql" 5 | "database/sql/driver" 6 | "encoding/json" 7 | "fmt" 8 | ) 9 | 10 | // NullFloat64 is null friendly type for float64. 11 | type NullFloat64 struct { 12 | f sql.NullFloat64 13 | } 14 | 15 | // NullFloat64Of return NullFloat64 that the value is set. 16 | func NullFloat64Of(value float64) NullFloat64 { 17 | var s NullFloat64 18 | s.Set(value) 19 | return s 20 | } 21 | 22 | // Valid return the value is valid. If true, it is not null value. 23 | func (f *NullFloat64) Valid() bool { 24 | return f.f.Valid 25 | } 26 | 27 | // Float64Value return the value. 28 | func (f *NullFloat64) Float64Value() float64 { 29 | return f.f.Float64 30 | } 31 | 32 | // Reset set nil to the value. 33 | func (f *NullFloat64) Reset() { 34 | f.f.Float64 = 0 35 | f.f.Valid = false 36 | } 37 | 38 | // Set set the value. 39 | func (f *NullFloat64) Set(value float64) *NullFloat64 { 40 | f.f.Valid = true 41 | f.f.Float64 = value 42 | return f 43 | } 44 | 45 | // Scan is a method for database/sql. 46 | func (f *NullFloat64) Scan(value interface{}) error { 47 | return f.f.Scan(value) 48 | } 49 | 50 | // String return string indicated the value. 51 | func (f NullFloat64) String() string { 52 | if !f.f.Valid { 53 | return "" 54 | } 55 | return fmt.Sprint(f.f.Float64) 56 | } 57 | 58 | // MarshalJSON encode the value to JSON. 59 | func (f NullFloat64) MarshalJSON() ([]byte, error) { 60 | if !f.f.Valid { 61 | return []byte("null"), nil 62 | } 63 | return json.Marshal(f.f.Float64) 64 | } 65 | 66 | // UnmarshalJSON decode data to the value. 67 | func (f *NullFloat64) UnmarshalJSON(data []byte) error { 68 | var value *float64 69 | if err := json.Unmarshal(data, &value); err != nil { 70 | return err 71 | } 72 | f.f.Valid = value != nil 73 | if value == nil { 74 | f.f.Float64 = 0 75 | } else { 76 | f.f.Float64 = *value 77 | } 78 | return nil 79 | } 80 | 81 | // Value implement driver.Valuer. 82 | func (f NullFloat64) Value() (driver.Value, error) { 83 | if !f.Valid() { 84 | return nil, nil 85 | } 86 | return f.f.Float64, nil 87 | } 88 | -------------------------------------------------------------------------------- /float64_test.go: -------------------------------------------------------------------------------- 1 | package nulltype 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "strings" 8 | "testing" 9 | ) 10 | 11 | func TestNullFloat64Stringer(t *testing.T) { 12 | var f NullFloat64 13 | 14 | want := "" 15 | got := fmt.Sprint(f) 16 | if got != want { 17 | t.Fatalf("want %v, but %v:", want, got) 18 | } 19 | 20 | want = "3.14" 21 | f.Set(3.14) 22 | got = fmt.Sprint(f) 23 | if got != want { 24 | t.Fatalf("want %v, but %v:", want, got) 25 | } 26 | 27 | want = "3.15" 28 | f = NullFloat64Of(3.15) 29 | got = fmt.Sprint(f) 30 | if got != want { 31 | t.Fatalf("want %v, but %v:", want, got) 32 | } 33 | 34 | want = "" 35 | f.Reset() 36 | got = fmt.Sprint(f) 37 | if got != want { 38 | t.Fatalf("want %v, but %v:", want, got) 39 | } 40 | } 41 | 42 | func TestNullFloat64MarshalJSON(t *testing.T) { 43 | var f NullFloat64 44 | 45 | var buf bytes.Buffer 46 | err := json.NewEncoder(&buf).Encode(f) 47 | if err != nil { 48 | t.Fatal(err) 49 | } 50 | 51 | want := "null" 52 | got := strings.TrimSpace(buf.String()) 53 | if got != want { 54 | t.Fatalf("want %v, but %v:", want, got) 55 | } 56 | 57 | buf.Reset() 58 | 59 | f.Set(3.14) 60 | err = json.NewEncoder(&buf).Encode(f) 61 | if err != nil { 62 | t.Fatal(err) 63 | } 64 | 65 | want = "3.14" 66 | got = strings.TrimSpace(buf.String()) 67 | if got != want { 68 | t.Fatalf("want %v, but %v:", want, got) 69 | } 70 | } 71 | 72 | func TestNullFloat64UnmarshalJSON(t *testing.T) { 73 | var f NullFloat64 74 | 75 | err := json.NewDecoder(strings.NewReader("null")).Decode(&f) 76 | if err != nil { 77 | t.Fatal(err) 78 | } 79 | 80 | if f.Valid() { 81 | t.Fatalf("must be null but got %v", f) 82 | } 83 | 84 | f.Set(3.14) 85 | 86 | err = json.NewDecoder(strings.NewReader(`3.14`)).Decode(&f) 87 | if err != nil { 88 | t.Fatal(err) 89 | } 90 | 91 | if !f.Valid() { 92 | t.Fatalf("must not be null but got nil") 93 | } 94 | 95 | want := 3.14 96 | got := f.Float64Value() 97 | if got != want { 98 | t.Fatalf("want %v, but %v:", want, got) 99 | } 100 | 101 | err = json.NewDecoder(strings.NewReader(`"foo"`)).Decode(&f) 102 | if err == nil { 103 | t.Fatal("should be fail") 104 | } 105 | } 106 | 107 | func TestNullFloat64ValueConverter(t *testing.T) { 108 | var f NullFloat64 109 | 110 | err := f.Scan("3.14") 111 | if err != nil { 112 | t.Fatal(err) 113 | } 114 | 115 | if !f.Valid() { 116 | t.Fatalf("must not be null but got nil") 117 | } 118 | 119 | want := 3.14 120 | got := f.Float64Value() 121 | if got != want { 122 | t.Fatalf("want %v, but %v:", want, got) 123 | } 124 | 125 | gotv, err := f.Value() 126 | if err != nil { 127 | t.Fatal(err) 128 | } 129 | if gotv != want { 130 | t.Fatalf("want %v, but %v:", want, got) 131 | } 132 | 133 | f.Reset() 134 | 135 | gotv, err = f.Value() 136 | if err != nil { 137 | t.Fatal(err) 138 | } 139 | if gotv != nil { 140 | t.Fatalf("must be null but got %v", gotv) 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mattn/go-nulltype 2 | 3 | go 1.10 4 | -------------------------------------------------------------------------------- /go.test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | echo "" > coverage.txt 5 | 6 | for d in $(go list ./... | grep -v vendor); do 7 | go test -race -coverprofile=profile.out -covermode=atomic "$d" 8 | if [ -f profile.out ]; then 9 | cat profile.out >> coverage.txt 10 | rm profile.out 11 | fi 12 | done 13 | -------------------------------------------------------------------------------- /int64.go: -------------------------------------------------------------------------------- 1 | package nulltype 2 | 3 | import ( 4 | "database/sql" 5 | "database/sql/driver" 6 | "encoding/json" 7 | "fmt" 8 | ) 9 | 10 | // NullInt64 is null friendly type for int64. 11 | type NullInt64 struct { 12 | i sql.NullInt64 13 | } 14 | 15 | // NullInt64Of return NullInt64 that the value is set. 16 | func NullInt64Of(value int64) NullInt64 { 17 | var s NullInt64 18 | s.Set(value) 19 | return s 20 | } 21 | 22 | // Valid return the value is valid. If true, it is not null value. 23 | func (i *NullInt64) Valid() bool { 24 | return i.i.Valid 25 | } 26 | 27 | // Int64Value return the value. 28 | func (i *NullInt64) Int64Value() int64 { 29 | return i.i.Int64 30 | } 31 | 32 | // Reset set nil to the value. 33 | func (i *NullInt64) Reset() { 34 | i.i.Int64 = 0 35 | i.i.Valid = false 36 | } 37 | 38 | // Set set the value. 39 | func (i *NullInt64) Set(value int64) *NullInt64 { 40 | i.i.Valid = true 41 | i.i.Int64 = value 42 | return i 43 | } 44 | 45 | // Scan is a method for database/sql. 46 | func (i *NullInt64) Scan(value interface{}) error { 47 | return i.i.Scan(value) 48 | } 49 | 50 | // String return string indicated the value. 51 | func (i NullInt64) String() string { 52 | if !i.i.Valid { 53 | return "" 54 | } 55 | return fmt.Sprint(i.i.Int64) 56 | } 57 | 58 | // MarshalJSON encode the value to JSON. 59 | func (i NullInt64) MarshalJSON() ([]byte, error) { 60 | if !i.i.Valid { 61 | return []byte("null"), nil 62 | } 63 | return json.Marshal(i.i.Int64) 64 | } 65 | 66 | // UnmarshalJSON decode data to the value. 67 | func (i *NullInt64) UnmarshalJSON(data []byte) error { 68 | var value *int64 69 | if err := json.Unmarshal(data, &value); err != nil { 70 | return err 71 | } 72 | i.i.Valid = value != nil 73 | if value == nil { 74 | i.i.Int64 = 0 75 | } else { 76 | i.i.Int64 = *value 77 | } 78 | return nil 79 | } 80 | 81 | // Value implement driver.Valuer. 82 | func (i NullInt64) Value() (driver.Value, error) { 83 | if !i.Valid() { 84 | return nil, nil 85 | } 86 | return i.i.Int64, nil 87 | } 88 | -------------------------------------------------------------------------------- /int64_test.go: -------------------------------------------------------------------------------- 1 | package nulltype 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "strings" 8 | "testing" 9 | ) 10 | 11 | func TestNullInt64Stringer(t *testing.T) { 12 | var i NullInt64 13 | 14 | want := "" 15 | got := fmt.Sprint(i) 16 | if got != want { 17 | t.Fatalf("want %v, but %v:", want, got) 18 | } 19 | 20 | want = "3" 21 | i.Set(3) 22 | got = fmt.Sprint(i) 23 | if got != want { 24 | t.Fatalf("want %v, but %v:", want, got) 25 | } 26 | 27 | want = "5" 28 | i = NullInt64Of(5) 29 | got = fmt.Sprint(i) 30 | if got != want { 31 | t.Fatalf("want %v, but %v:", want, got) 32 | } 33 | want = "" 34 | i.Reset() 35 | got = fmt.Sprint(i) 36 | if got != want { 37 | t.Fatalf("want %v, but %v:", want, got) 38 | } 39 | } 40 | 41 | func TestNullInt64MarshalJSON(t *testing.T) { 42 | var i NullInt64 43 | 44 | var buf bytes.Buffer 45 | err := json.NewEncoder(&buf).Encode(i) 46 | if err != nil { 47 | t.Fatal(err) 48 | } 49 | 50 | want := "null" 51 | got := strings.TrimSpace(buf.String()) 52 | if got != want { 53 | t.Fatalf("want %v, but %v:", want, got) 54 | } 55 | 56 | buf.Reset() 57 | 58 | i.Set(3) 59 | err = json.NewEncoder(&buf).Encode(i) 60 | if err != nil { 61 | t.Fatal(err) 62 | } 63 | 64 | want = "3" 65 | got = strings.TrimSpace(buf.String()) 66 | if got != want { 67 | t.Fatalf("want %v, but %v:", want, got) 68 | } 69 | } 70 | 71 | func TestNullInt64UnmarshalJSON(t *testing.T) { 72 | var i NullInt64 73 | 74 | err := json.NewDecoder(strings.NewReader("null")).Decode(&i) 75 | if err != nil { 76 | t.Fatal(err) 77 | } 78 | 79 | if i.Valid() { 80 | t.Fatalf("must be null but got %v", i) 81 | } 82 | 83 | err = json.NewDecoder(strings.NewReader(`3`)).Decode(&i) 84 | if err != nil { 85 | t.Fatal(err) 86 | } 87 | 88 | if !i.Valid() { 89 | t.Fatalf("must not be null but got nil") 90 | } 91 | 92 | want := int64(3) 93 | got := i.Int64Value() 94 | if got != want { 95 | t.Fatalf("want %v, but %v:", want, got) 96 | } 97 | 98 | err = json.NewDecoder(strings.NewReader(`"foo"`)).Decode(&i) 99 | if err == nil { 100 | t.Fatal("should be fail") 101 | } 102 | } 103 | 104 | func TestNullInt64ValueConverter(t *testing.T) { 105 | var i NullInt64 106 | 107 | err := i.Scan("3") 108 | if err != nil { 109 | t.Fatal(err) 110 | } 111 | 112 | if !i.Valid() { 113 | t.Fatalf("must not be null but got nil") 114 | } 115 | 116 | want := int64(3) 117 | got := i.Int64Value() 118 | if got != want { 119 | t.Fatalf("want %v, but %v:", want, got) 120 | } 121 | 122 | gotv, err := i.Value() 123 | if err != nil { 124 | t.Fatal(err) 125 | } 126 | if gotv != want { 127 | t.Fatalf("want %v, but %v:", want, got) 128 | } 129 | 130 | i.Reset() 131 | 132 | gotv, err = i.Value() 133 | if err != nil { 134 | t.Fatal(err) 135 | } 136 | if gotv != nil { 137 | t.Fatalf("must be null but got %v", gotv) 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /nulltype.go: -------------------------------------------------------------------------------- 1 | // Package nulltype implements the null-able types 2 | // that is friendly to json.Encoder, json.Decoder, database/sql, fmt.Stringer, 3 | // text/template, html/template, some of ORMs. All of types provided have 4 | // methods that is provided from fmt.Stringer, json.Marshaler, 5 | // json.Unmarshaler, database/sql.Valuer. 6 | package nulltype 7 | -------------------------------------------------------------------------------- /nulltype_test.go: -------------------------------------------------------------------------------- 1 | package nulltype 2 | 3 | import ( 4 | "bytes" 5 | "html/template" 6 | "testing" 7 | ) 8 | 9 | func TestTemplate(t *testing.T) { 10 | value := struct { 11 | Data1 string 12 | Data2 NullString 13 | }{} 14 | tpl, err := template.New("mytemplate").Parse(`{{.Data1}},{{.Data2}}`) 15 | if err != nil { 16 | t.Fatal(err) 17 | } 18 | var buf bytes.Buffer 19 | 20 | err = tpl.Execute(&buf, value) 21 | if err != nil { 22 | t.Fatal(err) 23 | } 24 | want := "," 25 | got := buf.String() 26 | if got != want { 27 | t.Fatalf("want %q, but %q:", want, got) 28 | } 29 | 30 | buf.Reset() 31 | value.Data1 = "data1" 32 | err = tpl.Execute(&buf, value) 33 | if err != nil { 34 | t.Fatal(err) 35 | } 36 | want = "data1," 37 | got = buf.String() 38 | if got != want { 39 | t.Fatalf("want %v, but %v:", want, got) 40 | } 41 | 42 | buf.Reset() 43 | value.Data2.Set("data2") 44 | err = tpl.Execute(&buf, value) 45 | if err != nil { 46 | t.Fatal(err) 47 | } 48 | want = "data1,data2" 49 | got = buf.String() 50 | if got != want { 51 | t.Fatalf("want %v, but %v:", want, got) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /string.go: -------------------------------------------------------------------------------- 1 | package nulltype 2 | 3 | import ( 4 | "database/sql" 5 | "database/sql/driver" 6 | "encoding/json" 7 | ) 8 | 9 | // NullString is null friendly type for string. 10 | type NullString struct { 11 | s sql.NullString 12 | } 13 | 14 | // NullStringOf return NullString that the value is set. 15 | func NullStringOf(value string) NullString { 16 | var s NullString 17 | s.Set(value) 18 | return s 19 | } 20 | 21 | // Valid return the value is valid. If true, it is not null value. 22 | func (s *NullString) Valid() bool { 23 | return s.s.Valid 24 | } 25 | 26 | // StringValue return the value. 27 | func (s *NullString) StringValue() string { 28 | return s.s.String 29 | } 30 | 31 | // Reset set nil to the value. 32 | func (s *NullString) Reset() { 33 | s.s.String = "" 34 | s.s.Valid = false 35 | } 36 | 37 | // Set set the value. 38 | func (s *NullString) Set(value string) { 39 | s.s.Valid = true 40 | s.s.String = value 41 | } 42 | 43 | // Scan is a method for database/sql. 44 | func (s *NullString) Scan(value interface{}) error { 45 | return s.s.Scan(value) 46 | } 47 | 48 | // String return string indicated the value. 49 | func (s NullString) String() string { 50 | if !s.s.Valid { 51 | return "" 52 | } 53 | return s.s.String 54 | } 55 | 56 | // MarshalJSON encode the value to JSON. 57 | func (s NullString) MarshalJSON() ([]byte, error) { 58 | if !s.s.Valid { 59 | return []byte("null"), nil 60 | } 61 | return json.Marshal(s.s.String) 62 | } 63 | 64 | // UnmarshalJSON decode data to the value. 65 | func (s *NullString) UnmarshalJSON(data []byte) error { 66 | var value *string 67 | if err := json.Unmarshal(data, &value); err != nil { 68 | return err 69 | } 70 | s.s.Valid = value != nil 71 | if value == nil { 72 | s.s.String = "" 73 | } else { 74 | s.s.String = *value 75 | } 76 | return nil 77 | } 78 | 79 | // Value implement driver.Valuer. 80 | func (s NullString) Value() (driver.Value, error) { 81 | if !s.Valid() { 82 | return nil, nil 83 | } 84 | return s.s.String, nil 85 | } 86 | -------------------------------------------------------------------------------- /string_test.go: -------------------------------------------------------------------------------- 1 | package nulltype 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "strings" 8 | "testing" 9 | ) 10 | 11 | func TestNullStringStringer(t *testing.T) { 12 | var s NullString 13 | 14 | want := "" 15 | got := fmt.Sprint(s) 16 | if got != want { 17 | t.Fatalf("want %v, but %v:", want, got) 18 | } 19 | 20 | want = "foo" 21 | s.Set("foo") 22 | got = fmt.Sprint(s) 23 | if got != want { 24 | t.Fatalf("want %v, but %v:", want, got) 25 | } 26 | 27 | want = "bar" 28 | s = NullStringOf("bar") 29 | got = fmt.Sprint(s) 30 | if got != want { 31 | t.Fatalf("want %v, but %v:", want, got) 32 | } 33 | 34 | want = "" 35 | s.Reset() 36 | got = fmt.Sprint(s) 37 | if got != want { 38 | t.Fatalf("want %v, but %v:", want, got) 39 | } 40 | } 41 | 42 | func TestNullStringMarshalJSON(t *testing.T) { 43 | var s NullString 44 | 45 | var buf bytes.Buffer 46 | err := json.NewEncoder(&buf).Encode(s) 47 | if err != nil { 48 | t.Fatal(err) 49 | } 50 | 51 | want := "null" 52 | got := strings.TrimSpace(buf.String()) 53 | if got != want { 54 | t.Fatalf("want %v, but %v:", want, got) 55 | } 56 | 57 | buf.Reset() 58 | 59 | s.Set("foo") 60 | err = json.NewEncoder(&buf).Encode(s) 61 | if err != nil { 62 | t.Fatal(err) 63 | } 64 | 65 | want = `"foo"` 66 | got = strings.TrimSpace(buf.String()) 67 | if got != want { 68 | t.Fatalf("want %v, but %v:", want, got) 69 | } 70 | } 71 | 72 | func TestNullStringUnmarshalJSON(t *testing.T) { 73 | var s NullString 74 | 75 | err := json.NewDecoder(strings.NewReader("null")).Decode(&s) 76 | if err != nil { 77 | t.Fatal(err) 78 | } 79 | 80 | if s.Valid() { 81 | t.Fatalf("must be null but got %v", s) 82 | } 83 | 84 | err = json.NewDecoder(strings.NewReader(`"foo"`)).Decode(&s) 85 | if err != nil { 86 | t.Fatal(err) 87 | } 88 | 89 | if !s.Valid() { 90 | t.Fatalf("must not be null but got nil") 91 | } 92 | 93 | want := "foo" 94 | got := s.StringValue() 95 | if got != want { 96 | t.Fatalf("want %v, but %v:", want, got) 97 | } 98 | 99 | err = json.NewDecoder(strings.NewReader("{}")).Decode(&s) 100 | if err == nil { 101 | t.Fatal("should be fail") 102 | } 103 | } 104 | 105 | func TestNullStringValueConverter(t *testing.T) { 106 | var s NullString 107 | 108 | err := s.Scan("1") 109 | if err != nil { 110 | t.Fatal(err) 111 | } 112 | 113 | if !s.Valid() { 114 | t.Fatalf("must not be null but got nil") 115 | } 116 | 117 | want := "1" 118 | got := s.StringValue() 119 | if got != want { 120 | t.Fatalf("want %v, but %v:", want, got) 121 | } 122 | 123 | gotv, err := s.Value() 124 | if err != nil { 125 | t.Fatal(err) 126 | } 127 | if gotv != want { 128 | t.Fatalf("want %v, but %v:", want, got) 129 | } 130 | 131 | s.Reset() 132 | 133 | gotv, err = s.Value() 134 | if err != nil { 135 | t.Fatal(err) 136 | } 137 | if gotv != nil { 138 | t.Fatalf("must be null but got %v", gotv) 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /time.go: -------------------------------------------------------------------------------- 1 | package nulltype 2 | 3 | import ( 4 | "database/sql" 5 | "database/sql/driver" 6 | "encoding/json" 7 | "time" 8 | ) 9 | 10 | // NullTime is null friendly type for string. 11 | type NullTime struct { 12 | t time.Time 13 | v bool // Valid is true if Time is not NULL 14 | } 15 | 16 | // NullTimeOf return NullTime that the value is set. 17 | func NullTimeOf(value time.Time) NullTime { 18 | var t NullTime 19 | t.Set(value) 20 | return t 21 | } 22 | 23 | // Valid return the value is valid. If true, it is not null value. 24 | func (t *NullTime) Valid() bool { 25 | return t.v 26 | } 27 | 28 | // TimeValue return the value. 29 | func (t *NullTime) TimeValue() time.Time { 30 | return t.t 31 | } 32 | 33 | // Reset set nil to the value. 34 | func (t *NullTime) Reset() { 35 | t.t = time.Unix(0, 0) 36 | t.v = false 37 | } 38 | 39 | // Set set the value. 40 | func (t *NullTime) Set(value time.Time) { 41 | t.v = true 42 | t.t = value 43 | } 44 | 45 | var timestampFormats = []string{ 46 | "2006-01-02 15:04:05.999999999-07:00", 47 | "2006-01-02T15:04:05.999999999-07:00", 48 | "2006-01-02 15:04:05.999999999", 49 | "2006-01-02T15:04:05.999999999", 50 | "2006-01-02 15:04:05", 51 | "2006-01-02T15:04:05", 52 | "2006-01-02 15:04", 53 | "2006-01-02T15:04", 54 | "2006-01-02", 55 | "2006/01/02 15:04:05", 56 | } 57 | 58 | // Scan is a method for database/sql. 59 | func (t *NullTime) Scan(value interface{}) error { 60 | t.t, t.v = value.(time.Time) 61 | if t.v { 62 | return nil 63 | } 64 | var ns sql.NullString 65 | if err := ns.Scan(value); err != nil { 66 | return err 67 | } 68 | if !ns.Valid { 69 | return nil 70 | } 71 | for _, tf := range timestampFormats { 72 | if tt, err := time.Parse(tf, ns.String); err == nil { 73 | t.t = tt 74 | t.v = true 75 | return nil 76 | } 77 | 78 | } 79 | return nil 80 | } 81 | 82 | // Time return string indicated the value. 83 | func (t NullTime) String() string { 84 | if !t.v { 85 | return "" 86 | } 87 | return t.t.Format("2006/01/02 15:04:05") 88 | } 89 | 90 | // MarshalJSON encode the value to JSON. 91 | func (t NullTime) MarshalJSON() ([]byte, error) { 92 | if !t.v { 93 | return []byte("null"), nil 94 | } 95 | return json.Marshal(t.t.Format(time.RFC3339)) 96 | } 97 | 98 | // UnmarshalJSON decode data to the value. 99 | func (t *NullTime) UnmarshalJSON(data []byte) error { 100 | var value *string 101 | if err := json.Unmarshal(data, &value); err != nil { 102 | return err 103 | } 104 | t.v = value != nil 105 | if value == nil { 106 | t.t = time.Unix(0, 0) 107 | } else { 108 | tt, err := time.Parse(time.RFC3339, *value) 109 | if err != nil { 110 | return err 111 | } 112 | t.t = tt 113 | } 114 | return nil 115 | } 116 | 117 | // Value implement driver.Valuer. 118 | func (t NullTime) Value() (driver.Value, error) { 119 | if !t.Valid() { 120 | return nil, nil 121 | } 122 | return t.t, nil 123 | } 124 | -------------------------------------------------------------------------------- /time_test.go: -------------------------------------------------------------------------------- 1 | package nulltype 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "strings" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | func TestNullTimeStringer(t *testing.T) { 13 | var nt NullTime 14 | 15 | want := "" 16 | got := fmt.Sprint(nt) 17 | if got != want { 18 | t.Fatalf("want %v, but %v:", want, got) 19 | } 20 | 21 | now := time.Now() 22 | want = now.Format("2006/01/02 15:04:05") 23 | nt.Set(now) 24 | got = fmt.Sprint(nt) 25 | if got != want { 26 | t.Fatalf("want %v, but %v:", want, got) 27 | } 28 | 29 | nt = NullTimeOf(now) 30 | got = fmt.Sprint(nt) 31 | if got != want { 32 | t.Fatalf("want %v, but %v:", want, got) 33 | } 34 | 35 | want = "" 36 | nt.Reset() 37 | got = fmt.Sprint(nt) 38 | if got != want { 39 | t.Fatalf("want %v, but %v:", want, got) 40 | } 41 | } 42 | 43 | func TestNullTimeMarshalJSON(t *testing.T) { 44 | var nt NullTime 45 | 46 | var buf bytes.Buffer 47 | err := json.NewEncoder(&buf).Encode(nt) 48 | if err != nil { 49 | t.Fatal(err) 50 | } 51 | 52 | want := "null" 53 | got := strings.TrimSpace(buf.String()) 54 | if got != want { 55 | t.Fatalf("want %v, but %v:", want, got) 56 | } 57 | 58 | buf.Reset() 59 | 60 | now := time.Now() 61 | nt.Set(now) 62 | err = json.NewEncoder(&buf).Encode(nt) 63 | if err != nil { 64 | t.Fatal(err) 65 | } 66 | 67 | want = `"` + now.Format(time.RFC3339) + `"` 68 | got = strings.TrimSpace(buf.String()) 69 | if got != want { 70 | t.Fatalf("want %v, but %v:", want, got) 71 | } 72 | } 73 | 74 | func TestNullTimeUnmarshalJSON(t *testing.T) { 75 | var nt NullTime 76 | 77 | err := json.NewDecoder(strings.NewReader("null")).Decode(&nt) 78 | if err != nil { 79 | t.Fatal(err) 80 | } 81 | 82 | if nt.Valid() { 83 | t.Fatalf("must be null but got %v", nt) 84 | } 85 | 86 | err = json.NewDecoder(strings.NewReader(`"2019-02-01T11:12:13Z"`)).Decode(&nt) 87 | if err != nil { 88 | t.Fatal(err) 89 | } 90 | 91 | if !nt.Valid() { 92 | t.Fatalf("must not be null but got nil") 93 | } 94 | 95 | now, _ := time.Parse(time.RFC3339, "2019-02-01T11:12:13Z") 96 | want := now 97 | got := nt.TimeValue() 98 | if got != want { 99 | t.Fatalf("want %v, but %v:", want, got) 100 | } 101 | 102 | err = json.NewDecoder(strings.NewReader("{}")).Decode(&nt) 103 | if err == nil { 104 | t.Fatal("should be fail") 105 | } 106 | 107 | err = json.NewDecoder(strings.NewReader(`"2019-02-01"`)).Decode(&nt) 108 | if err == nil { 109 | t.Fatal("should be fail") 110 | } 111 | } 112 | 113 | func TestNullTimeValueConverter(t *testing.T) { 114 | var nt NullTime 115 | 116 | now := time.Now() 117 | err := nt.Scan(now) 118 | if err != nil { 119 | t.Fatal(err) 120 | } 121 | 122 | if !nt.Valid() { 123 | t.Fatalf("must not be null but got nil") 124 | } 125 | 126 | want := now 127 | got := nt.TimeValue() 128 | if got != want { 129 | t.Fatalf("want %v, but %v:", want, got) 130 | } 131 | 132 | gotv, err := nt.Value() 133 | if err != nil { 134 | t.Fatal(err) 135 | } 136 | if gotv != want { 137 | t.Fatalf("want %v, but %v:", want, got) 138 | } 139 | 140 | nt.Reset() 141 | 142 | gotv, err = nt.Value() 143 | if err != nil { 144 | t.Fatal(err) 145 | } 146 | if gotv != nil { 147 | t.Fatalf("must be null but got %v", gotv) 148 | } 149 | 150 | err = nt.Scan("2019-02-19 17:53:00") 151 | if err != nil { 152 | t.Fatal(err) 153 | } 154 | wants := "2019-02-19 17:53:00" 155 | gots := nt.TimeValue().Format("2006-01-02 15:04:05") 156 | if gots != wants { 157 | t.Fatalf("want %v, but %v:", wants, gots) 158 | } 159 | 160 | err = nt.Scan("2019+02+19") 161 | if err != nil { 162 | t.Fatal(err) 163 | } 164 | 165 | err = nt.Scan(nil) 166 | if err != nil { 167 | t.Fatal(err) 168 | } 169 | 170 | var v complex64 171 | err = nt.Scan(v) 172 | if err == nil { 173 | t.Fatal("should be fail") 174 | } 175 | } 176 | --------------------------------------------------------------------------------