lukagiorgadze / gonull Goto Github PK
View Code? Open in Web Editor NEWGo package simplifies nullable fields handling using Go Generics.
Home Page: https://pkg.go.dev/github.com/LukaGiorgadze/gonull
License: MIT License
Go package simplifies nullable fields handling using Go Generics.
Home Page: https://pkg.go.dev/github.com/LukaGiorgadze/gonull
License: MIT License
In the provided code, when unmarshalling the JSON the Weight
attribute is correctly set to present: false, valid: false
as "Weight"
isn't specified in the JSON.
However, when marshalling the struct back to JSON the output includes the Weight
attribute with a value of null
. This is unexpected as I would assume Present: false
would omit the attribute from the marshalled JSON (and that the marshalled JSON should match the original JSON).
Steps to Reproduce
README.md
)package main
import (
"encoding/json"
"fmt"
"github.com/LukaGiorgadze/gonull"
)
type MyCustomInt int
type MyCustomFloat32 float32
type Person struct {
Name string
Age gonull.Nullable[MyCustomInt] // present: true, valid: true
Address gonull.Nullable[string] // present: true, valid: false
Height gonull.Nullable[MyCustomFloat32] // present: true, valid: false
Weight gonull.Nullable[string] // present: false, valid: false
}
func main() {
jsonData := []byte(`{"Name":"Alice","Age":15,"Address":null,"Height":null}`)
var person Person
err := json.Unmarshal(jsonData, &person)
if err != nil {
panic(err)
}
fmt.Printf("Unmarshalled Person: %+v\n", person)
marshalledData, err := json.Marshal(person)
if err != nil {
panic(err)
}
fmt.Printf("Marshalled JSON: %s\n", string(marshalledData))
}
Expected Behavior
The marshalled JSON should not include the Weight
attribute as it was not present in the original JSON (Present: false
):
{
"Name": "Alice",
"Age": 15,
"Address": null,
"Height": null
}
Actual Behavior
The marshalled JSON includes the Weight
attribute with a value of null
:
{
"Name": "Alice",
"Age": 15,
"Address": null,
"Height": null,
"Weight": null // <-- not expected, this should not be included
}
Environment
1.22
gonull
version: 1.2.0
Additional Context
This issue was discovered while testing the unmarshalling and marshalling of JSON data with optional fields in the Go Playground. The expected behavior is based on the assumption that Present: false
should result in the attribute being omitted from the marshalled JSON.
Error # 01: sql: Scan error on column index N, name COLUMN_NAME: unsupported type conversion.
This is an annoying JSON x Golang edge case but found a solution for this
Let's assume we have a struct like so:
type testStruct struct {
Foo gonull.Nullable[*string] `json:"foo"`
}
we want to be able to handle those cases:
{"foo":"f"}
{}
{"foo": null}
In the current api we cannot check if the field is present. As {}
and {"foo":null}
will result with Valid: false
and nil
value. However we can do this:
diff --git a/gonull.go b/gonull.go
index b20a29e..deab37d 100644
--- a/gonull.go
+++ b/gonull.go
@@ -19,8 +19,9 @@ var (
// It keeps track of the value (Val) and a flag (Valid) indicating whether the value has been set.
// This allows for better handling of nullable values, ensuring proper value management and serialization.
type Nullable[T any] struct {
- Val T
- Valid bool
+ Val T
+ Valid bool
+ Present bool
}
// NewNullable creates a new Nullable with the given value and sets Valid to true.
@@ -59,6 +60,7 @@ func (n Nullable[T]) Value() (driver.Value, error) {
// UnmarshalJSON implements the json.Unmarshaler interface for Nullable, allowing it to be used as a nullable field in JSON operations.
// This method ensures proper unmarshalling of JSON data into the Nullable value, correctly setting the Valid flag based on the JSON data.
func (n *Nullable[T]) UnmarshalJSON(data []byte) error {
+ n.Present = true
if string(data) == "null" {
n.Valid = false
return nil
Then we have this successful test:
func TestPresent(t *testing.T) {
var nullable1 testStruct
var nullable2 testStruct
var nullable3 testStruct
err := json.Unmarshal([]byte(`{"foo":"f"}`), &nullable1)
assert.NoError(t, err)
assert.Equal(t, true, nullable1.Foo.Valid)
assert.Equal(t, true, nullable1.Foo.Present)
err = json.Unmarshal([]byte(`{}`), &nullable2)
assert.NoError(t, err)
assert.Equal(t, false, nullable2.Foo.Valid)
assert.Equal(t, false, nullable3.Foo.Present)
assert.Nil(t, nullable2.Foo.Val)
err = json.Unmarshal([]byte(`{"foo": null}`), &nullable3)
assert.NoError(t, err)
assert.Equal(t, false, nullable3.Foo.Valid)
assert.Equal(t, true, nullable3.Foo.Present)
assert.Nil(t, nullable3.Foo.Val)
}
Happy to raise a PR if the implementation is ok.
Hi,
First thanks for the library.
I was wondering if it would be possible convert values to driver.Value supported types where possible.
For example when wrapping a uint32
value I get a non-Value type %T returned from Value
error when inserting a value into the DB.
I'm guessing this is due to the fact that in the driver.Valuer implementation the underlying value is returned as is and the standard library conversion isn't run.
https://github.com/golang/go/blob/66d34c7d08d7c536c3165dc49ed318e73ea5acc2/src/database/sql/driver/types.go#L243-L270
type TestEnum int
const (
TestEnumA TestEnum = iota
TestEnumB
)
type TestModel struct {
ID int
Field gonull.Nullable[TestEnum]
}
Scan fails with the following error
sql: Scan error on column index 1, name "field": unsupported type conversion
Thanks for providing a useful library.
Currently, I need to do the following to determine the default value
const defaultPath = "magic.dat" // somewhere in the package
optPath := gonull.NewNullable("hello.dat")
path := ""
if optPath.Valid {
path = optPath.Val
} else {
path = defaultPath
}
Suggestion is to add a new receiver method OrElse
so that this can be done in one line
path := optPath.OrElse(defaultPath)
Signature:
func (n *Nullable[T]) OrElse(value T) T
For example
func NullIsEqual[T comparable](a gonull.Nullable[T], b gonull.Nullable[T]) bool {
// If both are not present, they are considered equal
if !a.Present && !b.Present {
return true
}
// If both are present and values are equal, they are considered equal
return a.Present && b.Present && a.Val == b.Val
}
func NewNull[T comparable](value T) gonull.Nullable[T] {
return gonull.Nullable[T](gonull.NewNullable(value))
}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.