Skip to content

Commit

Permalink
test: add package example (#12)
Browse files Browse the repository at this point in the history
* refactor: improve convertToType readability

* refactor: use any instead of interface{}

* refactor: remove useless if statement

* test: add package example
  • Loading branch information
wazazaby authored Dec 30, 2023
1 parent 151a4a4 commit ed7f29e
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 17 deletions.
49 changes: 49 additions & 0 deletions examples/json.go
Original file line number Diff line number Diff line change
@@ -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}
}
27 changes: 13 additions & 14 deletions gonull.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
}

Expand Down Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions gonull_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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) {
Expand Down

0 comments on commit ed7f29e

Please sign in to comment.