Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 39 additions & 13 deletions nulltypes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
)

type TestStruct struct {
Name NullString
Name NullString `json:"Name,omitempty"`
IsMarried NullBool
Height NullFloat64
Age NullInt32
Expand All @@ -24,6 +24,8 @@ type TestStruct struct {
var tz, _ = time.LoadLocation("UTC")
var timeToday time.Time = time.Date(2020, 10, 16, 01, 04, 30, 0, tz)
var tStr = `{"Name":"John Doe","IsMarried":true,"Height":170.4,"Age":40,"Income":4000000,"BornAt":"2020-10-16T01:04:30Z","NameNull":null,"IsMarriedNull":null,"HeightNull":null,"AgeNull":null,"IncomeNull":null,"BornAtNull":null}`
var tStrWOmitEmpty = `{"Name":null,"IsMarried":true,"Height":170.4,"Age":40,"Income":4000000,"BornAt":"2020-10-16T01:04:30Z","NameNull":null,"IsMarriedNull":null,"HeightNull":null,"AgeNull":null,"IncomeNull":null,"BornAtNull":null}`

var tObj = TestStruct{
Name: NullString{String: "John Doe", Valid: true},
IsMarried: NullBool{Bool: true, Valid: true},
Expand All @@ -33,28 +35,52 @@ var tObj = TestStruct{
BornAt: NullTime{Time: timeToday, Valid: true},
}

func TestMarshalJSON(t *testing.T) {
var tObjWithEmptyString = TestStruct{
IsMarried: NullBool{Bool: true, Valid: true},
Height: NullFloat64{Float64: 170.4, Valid: true},
Age: NullInt32{Int32: 40, Valid: true},
Income: NullInt64{Int64: 4000000, Valid: true},
BornAt: NullTime{Time: timeToday, Valid: true},
}

func TestUnmarshalJSON(t *testing.T) {
// Test with all fields present
var ts1 TestStruct
b := []byte(tStr)
err := json.Unmarshal(b, &ts1)
err := json.Unmarshal([]byte(tStr), &ts1)
if err != nil {
t.Errorf("Something went bad while unmarshaling %+v obj...", tObj)
t.Errorf("Error unmarshaling JSON: %v", err)
}

t.Logf("%+v", ts1)

if ts1 != tObj {
t.Errorf("Unmarshalling string didn't work as expected, \n%+v\n!=\n%+v\n, somehow!", ts1, tObj)
t.Errorf("Unmarshaling didn't work as expected:\nGot: %+v\nWant: %+v", ts1, tObj)
}

// Test with Name field omitted (Name.Valid should be false)
var ts2 TestStruct
err = json.Unmarshal([]byte(tStrWOmitEmpty), &ts2)
if err != nil {
t.Errorf("Error unmarshaling JSON: %v", err)
}
if ts2 != tObjWithEmptyString {
t.Errorf("Unmarshaling didn't work as expected:\nGot: %+v\nWant: %+v", ts2, tObjWithEmptyString)
}
}

func TestUnmarshalJSON(t *testing.T) {
func TestMarshalJSON(t *testing.T) {
// Test marshaling with all fields present
b, err := json.Marshal(tObj)
if err != nil {
t.Errorf("Something went bad while marshaling %+v obj...", tObj)
t.Errorf("Error marshaling object: %v", err)
}

if string(b) != tStr {
t.Errorf("Marshalling string didn't work as expected, %v != %v, somehow!", string(b), tStr)
t.Errorf("Marshaling didn't work as expected:\nGot: %v\nWant: %v", string(b), tStr)
}

// Test marshaling with Name field omitted (Name.Valid is false)
b, err = json.Marshal(tObjWithEmptyString)
if err != nil {
t.Errorf("Error marshaling object: %v", err)
}
if string(b) != tStrWOmitEmpty {
t.Errorf("Marshaling didn't work as expected:\nGot: %v\nWant: %v", string(b), tStrWOmitEmpty)
}
}
33 changes: 19 additions & 14 deletions string.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package nulltypes
import (
"database/sql/driver"
"encoding/json"
"fmt"
)

// NullString is a wrapper around string
Expand All @@ -23,39 +24,43 @@ func String(s string) NullString {
// whenever it is of type NullString
func (ns NullString) MarshalJSON() ([]byte, error) {
if !ns.Valid {
return json.Marshal(nil)
// Return nil to ensure the field is omitted
return []byte("null"), nil
}
return json.Marshal(ns.String)
}

// UnmarshalJSON method is called by json.Unmarshal,
// whenever it is of type NullString
func (ns *NullString) UnmarshalJSON(b []byte) error {
var s *string
if string(b) == "null" {
ns.Valid = false
ns.String = ""
return nil
}
var s string
if err := json.Unmarshal(b, &s); err != nil {
return err
}
if s != nil {
ns.Valid = true
ns.String = *s
} else {
ns.Valid = false
}
ns.Valid = true
ns.String = s
return nil
}

// Scan satisfies the sql.scanner interface
// Scan satisfies the sql.Scanner interface
func (ns *NullString) Scan(value interface{}) error {
rt, ok := value.(string)
if ok {
*ns = NullString{rt, true}
} else {
switch v := value.(type) {
case string:
*ns = NullString{v, true}
case nil:
*ns = NullString{"", false}
default:
return fmt.Errorf("unable to scan type %T into NullString", value)
}
return nil
}

// Value satisfies the driver.Value interface
// Value satisfies the driver.Valuer interface
func (ns NullString) Value() (driver.Value, error) {
if ns.Valid {
return ns.String, nil
Expand Down