prefer JSON errors when unmarshaling, better docs
Invalid JSON input will return a *json.SyntaxError instead of a cryptic error from this package.
This commit is contained in:
parent
41fe781af5
commit
95e26ed907
17 changed files with 128 additions and 45 deletions
61
README.md
61
README.md
|
@ -7,52 +7,61 @@ There are two packages: `null` and its subpackage `zero`.
|
|||
|
||||
Types in `null` will only be considered null on null input, and will JSON encode to `null`. If you need zero and null be considered separate values, use these.
|
||||
|
||||
Types in `zero` are treated like zero values in Go: blank string input will produce a null `zero.String`, and null Strings will JSON encode to `""`. If you need zero and null treated the same, use these.
|
||||
Types in `zero` are treated like zero values in Go: blank string input will produce a null `zero.String`, and null Strings will JSON encode to `""`. Zero values of these types will be considered null to SQL. If you need zero and null treated the same, use these.
|
||||
|
||||
All types implement `sql.Scanner` and `driver.Valuer`, so you can use this library in place of `sql.NullXXX`. All types also implement: `encoding.TextMarshaler`, `encoding.TextUnmarshaler`, `json.Marshaler`, and `json.Unmarshaler`.
|
||||
|
||||
#### zero.String
|
||||
A nullable string.
|
||||
### null package
|
||||
|
||||
Will marshal to a blank string if null. Blank string input produces a null String. In other words, null values and empty values are considered equivalent. Can unmarshal from `sql.NullString` JSON input.
|
||||
|
||||
#### zero.Int
|
||||
A nullable int64.
|
||||
|
||||
Will marshal to 0 if null. Blank string or 0 input produces a null Int. In other words, null values and empty values are considered equivalent. Can unmarshal from `sql.NullInt64` JSON input.
|
||||
|
||||
#### zero.Float
|
||||
A nullable float64.
|
||||
|
||||
Will marshal to 0 if null. Blank string or 0 input produces a null Float. In other words, null values and empty values are considered equivalent. Can unmarshal from `sql.NullFloat64` JSON input.
|
||||
|
||||
#### zero.Bool
|
||||
A nullable bool.
|
||||
|
||||
Will marshal to false if null. Blank string or false input produces a null Float. In other words, null values and empty values are considered equivalent. Can unmarshal from `sql.NullBool` JSON input.
|
||||
`import "gopkg.in/guregu/null.v2"`
|
||||
|
||||
#### null.String
|
||||
An even nuller nullable string.
|
||||
Nullable string.
|
||||
|
||||
Unlike `zero.String`, `null.String` will marshal to null if null. Zero (blank) input will not produce a null String. Can unmarshal from `sql.NullString` JSON input.
|
||||
Marshals to JSON null if SQL source data is null. Zero (blank) input will not produce a null String. Can unmarshal from `sql.NullString` JSON input or string input.
|
||||
|
||||
#### null.Int
|
||||
An even nuller nullable int64.
|
||||
Nullable int64.
|
||||
|
||||
Unlike `zero.Int`, `null.Int` will marshal to null if null. Zero input will not produce a null Int. Can unmarshal from `sql.NullInt64` JSON input.
|
||||
Marshals to JSON null if SQL null. Zero input will not produce a null Int. Can unmarshal from `sql.NullInt64` JSON input.
|
||||
|
||||
#### null.Float
|
||||
An even nuller nullable float64.
|
||||
Nullable float64.
|
||||
|
||||
Unlike `zero.Float`, `null.Float` will marshal to null if null. Zero input will not produce a null Float. Can unmarshal from `sql.NullFloat64` JSON input.
|
||||
|
||||
#### null.Bool
|
||||
An even nuller nullable float64.
|
||||
Nullable bool.
|
||||
|
||||
Unlike `zero.Bool`, `null.Bool` will marshal to null if null. False input will not produce a null Bool. Can unmarshal from `sql.NullBool` JSON input.
|
||||
|
||||
### zero package
|
||||
|
||||
`import "gopkg.in/guregu/null.v2/zero"`
|
||||
|
||||
#### zero.String
|
||||
Nullable int64.
|
||||
|
||||
Will marshal to a blank string if null. Blank string input produces a null String. Null values and zero values are considered equivalent. Can unmarshal from `sql.NullString` JSON input.
|
||||
|
||||
#### zero.Int
|
||||
Nullable int64.
|
||||
|
||||
Will marshal to 0 if null. Blank string or 0 input produces a null Int. Null values and zero values are considered equivalent. Can unmarshal from `sql.NullInt64` JSON input.
|
||||
|
||||
#### zero.Float
|
||||
Nullable float64.
|
||||
|
||||
Will marshal to 0 if null. Blank string or 0 input produces a null Float. Null values and zero values are considered equivalent. Can unmarshal from `sql.NullFloat64` JSON input.
|
||||
|
||||
#### zero.Bool
|
||||
Nullable bool.
|
||||
|
||||
Will marshal to false if null. Blank string, false input produces a null Float. Null values and zero values are considered equivalent. Can unmarshal from `sql.NullBool` JSON input.
|
||||
|
||||
|
||||
### Bugs
|
||||
`json`'s `",omitempty"` struct tag does not work correctly right now. It will never omit a null or empty String. This should be [fixed eventually](https://github.com/golang/go/issues/4357).
|
||||
`json`'s `",omitempty"` struct tag does not work correctly right now. It will never omit a null or empty String. This might be [fixed eventually](https://github.com/golang/go/issues/4357).
|
||||
|
||||
### License
|
||||
BSD
|
4
bool.go
4
bool.go
|
@ -45,7 +45,9 @@ func BoolFromPtr(b *bool) Bool {
|
|||
func (b *Bool) UnmarshalJSON(data []byte) error {
|
||||
var err error
|
||||
var v interface{}
|
||||
json.Unmarshal(data, &v)
|
||||
if err = json.Unmarshal(data, &v); err != nil {
|
||||
return err
|
||||
}
|
||||
switch x := v.(type) {
|
||||
case bool:
|
||||
b.Bool = x
|
||||
|
|
|
@ -53,6 +53,12 @@ func TestUnmarshalBool(t *testing.T) {
|
|||
panic("err should not be nil")
|
||||
}
|
||||
assertNullBool(t, badType, "wrong type json")
|
||||
|
||||
var invalid Bool
|
||||
err = invalid.UnmarshalJSON(invalidJSON)
|
||||
if _, ok := err.(*json.SyntaxError); !ok {
|
||||
t.Errorf("expected json.SyntaxError, not %T", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTextUnmarshalBool(t *testing.T) {
|
||||
|
|
4
float.go
4
float.go
|
@ -45,7 +45,9 @@ func FloatFromPtr(f *float64) Float {
|
|||
func (f *Float) UnmarshalJSON(data []byte) error {
|
||||
var err error
|
||||
var v interface{}
|
||||
json.Unmarshal(data, &v)
|
||||
if err = json.Unmarshal(data, &v); err != nil {
|
||||
return err
|
||||
}
|
||||
switch x := v.(type) {
|
||||
case float64:
|
||||
f.Float64 = float64(x)
|
||||
|
|
|
@ -52,6 +52,12 @@ func TestUnmarshalFloat(t *testing.T) {
|
|||
panic("err should not be nil")
|
||||
}
|
||||
assertNullFloat(t, badType, "wrong type json")
|
||||
|
||||
var invalid Float
|
||||
err = invalid.UnmarshalJSON(invalidJSON)
|
||||
if _, ok := err.(*json.SyntaxError); !ok {
|
||||
t.Errorf("expected json.SyntaxError, not %T", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTextUnmarshalFloat(t *testing.T) {
|
||||
|
|
4
int.go
4
int.go
|
@ -45,7 +45,9 @@ func IntFromPtr(i *int64) Int {
|
|||
func (i *Int) UnmarshalJSON(data []byte) error {
|
||||
var err error
|
||||
var v interface{}
|
||||
json.Unmarshal(data, &v)
|
||||
if err = json.Unmarshal(data, &v); err != nil {
|
||||
return err
|
||||
}
|
||||
switch v.(type) {
|
||||
case float64:
|
||||
// Unmarshal again, directly to int64, to avoid intermediate float64
|
||||
|
|
|
@ -54,6 +54,13 @@ func TestUnmarshalInt(t *testing.T) {
|
|||
panic("err should not be nil")
|
||||
}
|
||||
assertNullInt(t, badType, "wrong type json")
|
||||
|
||||
var invalid Int
|
||||
err = invalid.UnmarshalJSON(invalidJSON)
|
||||
if _, ok := err.(*json.SyntaxError); !ok {
|
||||
t.Errorf("expected json.SyntaxError, not %T", err)
|
||||
}
|
||||
assertNullInt(t, invalid, "invalid json")
|
||||
}
|
||||
|
||||
func TestUnmarshalNonIntegerNumber(t *testing.T) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Package null contains types that consider zero input and null input as separate values.
|
||||
// Package null contains SQL types that consider zero input and null input as separate values,
|
||||
// with convenient support for JSON and text marshaling.
|
||||
// Types in this package will always encode to their null value if null.
|
||||
// Use the zero subpackage if you want empty and null to be treated the same.
|
||||
// Use the zero subpackage if you want zero values and null to be treated the same.
|
||||
package null
|
||||
|
||||
import (
|
||||
|
@ -45,7 +46,9 @@ func NewString(s string, valid bool) String {
|
|||
func (s *String) UnmarshalJSON(data []byte) error {
|
||||
var err error
|
||||
var v interface{}
|
||||
json.Unmarshal(data, &v)
|
||||
if err = json.Unmarshal(data, &v); err != nil {
|
||||
return err
|
||||
}
|
||||
switch x := v.(type) {
|
||||
case string:
|
||||
s.String = x
|
||||
|
|
|
@ -9,7 +9,9 @@ var (
|
|||
stringJSON = []byte(`"test"`)
|
||||
blankStringJSON = []byte(`""`)
|
||||
nullStringJSON = []byte(`{"String":"test","Valid":true}`)
|
||||
nullJSON = []byte(`null`)
|
||||
|
||||
nullJSON = []byte(`null`)
|
||||
invalidJSON = []byte(`:)`)
|
||||
)
|
||||
|
||||
type stringInStruct struct {
|
||||
|
@ -63,6 +65,13 @@ func TestUnmarshalString(t *testing.T) {
|
|||
panic("err should not be nil")
|
||||
}
|
||||
assertNullStr(t, badType, "wrong type json")
|
||||
|
||||
var invalid String
|
||||
err = invalid.UnmarshalJSON(invalidJSON)
|
||||
if _, ok := err.(*json.SyntaxError); !ok {
|
||||
t.Errorf("expected json.SyntaxError, not %T", err)
|
||||
}
|
||||
assertNullStr(t, invalid, "invalid json")
|
||||
}
|
||||
|
||||
func TestTextUnmarshalString(t *testing.T) {
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
)
|
||||
|
||||
// Bool is a nullable bool. False input is considered null.
|
||||
// JSON marshals to false if null.
|
||||
// Considered null to SQL unmarshaled from a false value.
|
||||
type Bool struct {
|
||||
sql.NullBool
|
||||
|
@ -43,7 +44,9 @@ func BoolFromPtr(b *bool) Bool {
|
|||
func (b *Bool) UnmarshalJSON(data []byte) error {
|
||||
var err error
|
||||
var v interface{}
|
||||
json.Unmarshal(data, &v)
|
||||
if err = json.Unmarshal(data, &v); err != nil {
|
||||
return err
|
||||
}
|
||||
switch x := v.(type) {
|
||||
case bool:
|
||||
b.Bool = x
|
||||
|
|
|
@ -9,7 +9,6 @@ var (
|
|||
boolJSON = []byte(`true`)
|
||||
falseJSON = []byte(`false`)
|
||||
nullBoolJSON = []byte(`{"Bool":true,"Valid":true}`)
|
||||
invalidJSON = []byte(`:)`)
|
||||
)
|
||||
|
||||
func TestBoolFrom(t *testing.T) {
|
||||
|
@ -54,9 +53,9 @@ func TestUnmarshalBool(t *testing.T) {
|
|||
assertNullBool(t, null, "null json")
|
||||
|
||||
var invalid Bool
|
||||
err = invalid.UnmarshalText(invalidJSON)
|
||||
if err == nil {
|
||||
panic("err should not be nil")
|
||||
err = invalid.UnmarshalJSON(invalidJSON)
|
||||
if _, ok := err.(*json.SyntaxError); !ok {
|
||||
t.Errorf("expected json.SyntaxError, not %T: %v", err, err)
|
||||
}
|
||||
assertNullBool(t, invalid, "invalid json")
|
||||
|
||||
|
@ -88,6 +87,12 @@ func TestTextUnmarshalBool(t *testing.T) {
|
|||
err = null.UnmarshalText(nullJSON)
|
||||
maybePanic(err)
|
||||
assertNullBool(t, null, `UnmarshalText() "null"`)
|
||||
|
||||
var invalid Bool
|
||||
err = invalid.UnmarshalText(invalidJSON)
|
||||
if err == nil {
|
||||
panic("err should not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalBool(t *testing.T) {
|
||||
|
|
|
@ -45,7 +45,9 @@ func FloatFromPtr(f *float64) Float {
|
|||
func (f *Float) UnmarshalJSON(data []byte) error {
|
||||
var err error
|
||||
var v interface{}
|
||||
json.Unmarshal(data, &v)
|
||||
if err = json.Unmarshal(data, &v); err != nil {
|
||||
return err
|
||||
}
|
||||
switch x := v.(type) {
|
||||
case float64:
|
||||
f.Float64 = x
|
||||
|
|
|
@ -57,6 +57,13 @@ func TestUnmarshalFloat(t *testing.T) {
|
|||
panic("err should not be nil")
|
||||
}
|
||||
assertNullFloat(t, badType, "wrong type json")
|
||||
|
||||
var invalid Float
|
||||
err = invalid.UnmarshalJSON(invalidJSON)
|
||||
if _, ok := err.(*json.SyntaxError); !ok {
|
||||
t.Errorf("expected json.SyntaxError, not %T", err)
|
||||
}
|
||||
assertNullFloat(t, invalid, "invalid json")
|
||||
}
|
||||
|
||||
func TestTextUnmarshalFloat(t *testing.T) {
|
||||
|
|
|
@ -46,7 +46,9 @@ func IntFromPtr(i *int64) Int {
|
|||
func (i *Int) UnmarshalJSON(data []byte) error {
|
||||
var err error
|
||||
var v interface{}
|
||||
json.Unmarshal(data, &v)
|
||||
if err = json.Unmarshal(data, &v); err != nil {
|
||||
return err
|
||||
}
|
||||
switch v.(type) {
|
||||
case float64:
|
||||
// Unmarshal again, directly to int64, to avoid intermediate float64
|
||||
|
|
|
@ -60,6 +60,13 @@ func TestUnmarshalInt(t *testing.T) {
|
|||
panic("err should not be nil")
|
||||
}
|
||||
assertNullInt(t, badType, "wrong type json")
|
||||
|
||||
var invalid Int
|
||||
err = invalid.UnmarshalJSON(invalidJSON)
|
||||
if _, ok := err.(*json.SyntaxError); !ok {
|
||||
t.Errorf("expected json.SyntaxError, not %T", err)
|
||||
}
|
||||
assertNullInt(t, invalid, "invalid json")
|
||||
}
|
||||
|
||||
func TestUnmarshalNonIntegerNumber(t *testing.T) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// Package zero provides a convenient way of handling null values.
|
||||
// Types in this package consider empty or zero input the same as null input.
|
||||
// Types in this package will encode to their zero value, even if null.
|
||||
// Package zero contains SQL types that consider zero input and null input to be equivalent
|
||||
// with convenient support for JSON and text marshaling.
|
||||
// Types in this package will JSON marshal to their zero value, even if null.
|
||||
// Use the null parent package if you don't want this.
|
||||
package zero
|
||||
|
||||
|
@ -48,7 +48,9 @@ func StringFromPtr(s *string) String {
|
|||
func (s *String) UnmarshalJSON(data []byte) error {
|
||||
var err error
|
||||
var v interface{}
|
||||
json.Unmarshal(data, &v)
|
||||
if err = json.Unmarshal(data, &v); err != nil {
|
||||
return err
|
||||
}
|
||||
switch x := v.(type) {
|
||||
case string:
|
||||
s.String = x
|
||||
|
|
|
@ -9,7 +9,9 @@ var (
|
|||
stringJSON = []byte(`"test"`)
|
||||
blankStringJSON = []byte(`""`)
|
||||
nullStringJSON = []byte(`{"String":"test","Valid":true}`)
|
||||
nullJSON = []byte(`null`)
|
||||
|
||||
nullJSON = []byte(`null`)
|
||||
invalidJSON = []byte(`:)`)
|
||||
)
|
||||
|
||||
type stringInStruct struct {
|
||||
|
@ -51,6 +53,13 @@ func TestUnmarshalString(t *testing.T) {
|
|||
panic("err should not be nil")
|
||||
}
|
||||
assertNullStr(t, badType, "wrong type json")
|
||||
|
||||
var invalid String
|
||||
err = invalid.UnmarshalJSON(invalidJSON)
|
||||
if _, ok := err.(*json.SyntaxError); !ok {
|
||||
t.Errorf("expected json.SyntaxError, not %T", err)
|
||||
}
|
||||
assertNullStr(t, invalid, "invalid json")
|
||||
}
|
||||
|
||||
func TestTextUnmarshalString(t *testing.T) {
|
||||
|
|
Loading…
Add table
Reference in a new issue