From ed7f29e4e3b5903817c18c9453c5bd3c4ec10a03 Mon Sep 17 00:00:00 2001 From: Teddy Sommavilla <36576716+wazazaby@users.noreply.github.com> Date: Sat, 30 Dec 2023 16:59:35 +0100 Subject: [PATCH] test: add package example (#12) * refactor: improve convertToType readability * refactor: use any instead of interface{} * refactor: remove useless if statement * test: add package example --- examples/json.go | 49 ++++++++++++++++++++++++++++++++++++++++++++++++ gonull.go | 27 +++++++++++++------------- gonull_test.go | 6 +++--- 3 files changed, 65 insertions(+), 17 deletions(-) create mode 100644 examples/json.go diff --git a/examples/json.go b/examples/json.go new file mode 100644 index 0000000..34ebfc2 --- /dev/null +++ b/examples/json.go @@ -0,0 +1,49 @@ +package examples + +import ( + "encoding/json" + "fmt" + + "github.com/LukaGiorgadze/gonull" +) + +type MyCustomInt int +type MyCustomFloat32 float32 + +type Person struct { + Name string `json:"name"` + Age gonull.Nullable[MyCustomInt] `json:"age"` + Address gonull.Nullable[string] `json:"address"` + Height gonull.Nullable[MyCustomFloat32] `json:"height"` +} + +func Example() { + jsonData := []byte(`{"name":"Alice","age":15,"address":null,"height":null}`) + + var person Person + if err := json.Unmarshal(jsonData, &person); err != nil { + panic(err) + } + + // Age is present and valid. + fmt.Printf("Person.Age is valid: %t, present: %t\n", person.Age.Valid, person.Age.Present) + + // Address is present but invalid (explicit null). + fmt.Printf("Person.Address is valid: %t, present: %t\n", person.Address.Valid, person.Address.Present) + + // Same for the height. + fmt.Printf("Person.Height is valid: %t, present: %t\n", person.Height.Valid, person.Height.Present) + + marshalledData, err := json.Marshal(person) + if err != nil { + panic(err) + } + // Null values will be kept when marshalling to JSON. + fmt.Println(string(marshalledData)) + + // Output: + // Person.Age is valid: true, present: true + // Person.Address is valid: false, present: true + // Person.Height is valid: false, present: true + // {"name":"Alice","age":15,"address":null,"height":null} +} diff --git a/gonull.go b/gonull.go index 9ea9501..19795a8 100644 --- a/gonull.go +++ b/gonull.go @@ -34,7 +34,7 @@ func NewNullable[T any](value T) Nullable[T] { // Scan implements the sql.Scanner interface for Nullable, allowing it to be used as a nullable field in database operations. // It is responsible for properly setting the Valid flag and converting the scanned value to the target type T. // This enables seamless integration with database/sql when working with nullable values. -func (n *Nullable[T]) Scan(value interface{}) error { +func (n *Nullable[T]) Scan(value any) error { n.Present = true if value == nil { @@ -45,9 +45,7 @@ func (n *Nullable[T]) Scan(value interface{}) error { var err error n.Val, err = convertToType[T](value) - if err == nil { - n.Valid = true - } + n.Valid = err == nil return err } @@ -99,25 +97,26 @@ func zeroValue[T any]() T { // convertToType is a helper function that attempts to convert the given value to type T. // This function is used by Scan to properly handle value conversion, ensuring that Nullable values are always of the correct type. -func convertToType[T any](value interface{}) (T, error) { +func convertToType[T any](value any) (T, error) { var zero T if value == nil { return zero, nil } - if reflect.TypeOf(value) == reflect.TypeOf(zero) { + valueType := reflect.TypeOf(value) + targetType := reflect.TypeOf(zero) + if valueType == targetType { return value.(T), nil } + isNumeric := func(kind reflect.Kind) bool { + return kind >= reflect.Int && kind <= reflect.Float64 + } + // Check if the value is a numeric type and if T is also a numeric type. - valueType := reflect.TypeOf(value) - targetType := reflect.TypeOf(zero) - if valueType.Kind() >= reflect.Int && valueType.Kind() <= reflect.Float64 && - targetType.Kind() >= reflect.Int && targetType.Kind() <= reflect.Float64 { - if valueType.ConvertibleTo(targetType) { - convertedValue := reflect.ValueOf(value).Convert(targetType) - return convertedValue.Interface().(T), nil - } + if isNumeric(valueType.Kind()) && isNumeric(targetType.Kind()) { + convertedValue := reflect.ValueOf(value).Convert(targetType) + return convertedValue.Interface().(T), nil } return zero, ErrUnsupportedConversion diff --git a/gonull_test.go b/gonull_test.go index 3016033..8cc9ec1 100644 --- a/gonull_test.go +++ b/gonull_test.go @@ -24,7 +24,7 @@ type NullableInt struct { func TestNullableScan(t *testing.T) { tests := []struct { name string - value interface{} + value any Valid bool Present bool wantErr bool @@ -283,7 +283,7 @@ func TestNullableScanWithCustomEnum(t *testing.T) { func TestConvertToTypeWithNilValue(t *testing.T) { tests := []struct { name string - expected interface{} + expected any }{ { name: "Nil to int", @@ -345,7 +345,7 @@ func TestConvertToTypeWithNilValue(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - var result interface{} + var result any var err error switch tc.expected.(type) {