null

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 24, 2023 License: MIT Imports: 9 Imported by: 0

README

GitHub release (latest by date)

Null

Null is a package that provides a generic nullable variable that can be used for both JSON and SQL operations.

Features

  • Uses a generic type for the underlying value
  • Provides an easy way to test if certain struct fields were set after unmarshalling a JSON message
  • Makes it easier to update only those fields in the database which were set previously. Especially in NoSQL databases using JSON documents
  • Compatible with the standard database/sql package
    • If the underlying type is a custom type that the standard database/sql package can't handle, then it is possible to implement the sql.Scanner and driver.Valuer interfaces for that given type to be compatible
  • Possible to control how the underlying value is handled during json marshalling and unmarshalling by implementing the json.Marshaler and json.Unmarshaler interfaces for the underlying type

Installation

go get -u github.com/mauserzjeh/null

Usage & Examples

Below you can see how to use the package in general and also in a more complex scenario. There are a few examples in test_null.go as well.

General usage
var nullableStr null.Var[string]

// IsSet returns if the value was set
nullableStr.IsSet() // false

// Valid returns if the value is NULL
nullableStr.Valid() // false

// Val returns the value
nullableStr.Val() // ""

// ------------------------------------------------------------------

nullableStr.Set("foo")
nullableStr.IsSet() // true
nullableStr.Valid() // true
nullableStr.Val() // "foo"

// ------------------------------------------------------------------

nullableStr.SetNil()
nullableStr.IsSet() // true
nullableStr.Valid() // false
nullableStr.Val() // ""

// ------------------------------------------------------------------

nullableStr.Unset()
nullableStr.IsSet() // false
nullableStr.Valid() // false
nullableStr.Val() // ""
Complex usage
  1. Let's have the following types
// custom integer type
type SiblingType int64

const (
    Sister SiblingType = iota
    Brother
)

// custom slice type
type CustomSlice []string

// custom generic map
type CustomMap[K, V comparable] map[K]V

// custom struct
type Sibling struct {
    Name null.Var[string]      `json:"name"`
    Age  null.Var[int64]       `json:"age"`
    Type null.Var[SiblingType] `json:"type"`
}

// another custom struct
type Person struct {
    Name       null.Var[string]                   `json:"name"`
    Age        null.Var[int64]                    `json:"age"`
    BirthDate  null.Var[time.Time]                `json:"birth_date"`
    Books      null.Var[CustomSlice]              `json:"books"`
    ExamScores null.Var[CustomMap[string, int64]] `json:"exam_scores"`
    Sibling    null.Var[Sibling]                  `json:"sibling"`
}
  1. Default JSON marshaling
var p Person
log.Printf("%+v", p)
// {
//     Name:       {set:false valid:false value:} 
//     Age:        {set:false valid:false value:0} 
//     BirthDate:  {set:false valid:false value:{wall:0 ext:0 loc:<nil>}} 
//     Books:      {set:false valid:false value:[]} 
//     ExamScores: {set:false valid:false value:map[]} 
//     Sibling:    {set:false valid:false value:{
//             Name:   {set:false valid:false value:} 
//             Age:    {set:false valid:false value:0} 
//             Type:   {set:false valid:false value:0}
//         }
//     }
// }

j, _ := json.Marshal(p)
log.Printf("%s", j)
// {
// 	"name":null,
// 	"age":null,
// 	"birth_date":null,
// 	"books":null,
// 	"exam_scores":null,
// 	"sibling":null
// }

p.Name.Set("Peter")
p.Age.Set(25)

var s Sibling
log.Printf("%+v", s)
// {
//     Name:   {set:false valid:false value:} 
//     Age:    {set:false valid:false value:0} 
//     Type:   {set:false valid:false value:0}
// }

s.Name.Set("Anna")
s.Age.Set(20)
s.Type.Set(Sister)

p.Sibling.Set(s)

j, _ = json.Marshal(p)
log.Printf("%s", j)
// {
// 	"name":"Peter",
// 	"age":25,
// 	"birth_date":null,
// 	"books":null,
// 	"exam_scores":null,
// 	"sibling":{
// 		"name":"Anna",
// 		"age":20,
// 		"type":0
// 	}
// }

p.BirthDate.Set(time.Unix(852073200, 0))
p.Books.Set([]string{"George Orwell - 1984", "Stephen E. Ambrose - Band of Brothers"})
p.ExamScores.Set(CustomMap[string, int64]{
    "math":    80,
    "physics": 90,
})

j, _ = json.Marshal(p)
log.Printf("%s", j)
// {
// 	"name":"Peter",
// 	"age":25,
// 	"birth_date":"1997-01-01T00:00:00+01:00",
// 	"books":[
// 		"George Orwell - 1984",
// 		"Stephen E. Ambrose - Band of Brothers"
// 	],
// 	"exam_scores":{
// 		"math":80,
// 		"physics":90
// 	},
// 	"sibling":{
// 		"name":"Anna",
// 		"age":20,
// 		"type":0
// 	}
// }
  1. Implement JSON marshaler for Person and Sibling
// NOTE: 
// It is better to implement a generic function that can turn a struct into a map. 
// Then in the MarshalJSON implementations, turn the structure into a map, loop through each item and 
// do a type switch for nullable variables (each type of null.Var used in the original struct should have a case) and
// just filter out those variables which were not set. 
// To keep this example clean, we will just manually check each field instead.

// MarshalJSON implements the json.Marshaler interface
func (s Sibling) MarshalJSON() ([]byte, error) {
    m := map[string]any{}
    if s.Name.IsSet() {
        m["name"] = s.Name
    }

    if s.Age.IsSet() {
        m["age"] = s.Age
    }

    if s.Type.IsSet() {
        m["type"] = s.Type
    }

    return json.Marshal(m)
}

// MarshalJSON implements the json.Marshaler interface
func (p Person) MarshalJSON() ([]byte, error) {
    m := map[string]any{}
    if p.Name.IsSet() {
        m["name"] = p.Name
    }

    if p.Age.IsSet() {
        m["age"] = p.Age
    }

    if p.BirthDate.IsSet() {
        m["birth_date"] = p.BirthDate
    }

    if p.Books.IsSet() {
        m["books"] = p.Books
    }

    if p.ExamScores.IsSet() {
        m["exam_scores"] = p.ExamScores
    }

    if p.Sibling.IsSet() {
        m["sibling"] = p.Sibling
    }

    return json.Marshal(m)
}

var p Person
log.Printf("%+v", p)
// {
//     Name:       {set:false valid:false value:} 
//     Age:        {set:false valid:false value:0} 
//     BirthDate:  {set:false valid:false value:{wall:0 ext:0 loc:<nil>}} 
//     Books:      {set:false valid:false value:[]} 
//     ExamScores: {set:false valid:false value:map[]} 
//     Sibling:    {set:false valid:false value:{
//             Name:   {set:false valid:false value:} 
//             Age:    {set:false valid:false value:0} 
//             Type:   {set:false valid:false value:0}
//         }
//     }
// }

j, _ := json.Marshal(p)
log.Printf("%s", j)
// {}

p.Name.Set("Peter")
p.Age.Set(25)

var s Sibling
log.Printf("%+v", s)
// {
//     Name:   {set:false valid:false value:} 
//     Age:    {set:false valid:false value:0} 
//     Type:   {set:false valid:false value:0}
// }

s.Name.Set("Anna")
s.Age.Set(20)
s.Type.Set(Sister)

p.Sibling.Set(s)

j, _ = json.Marshal(p)
log.Printf("%s", j)
// {
//     "age":25,
//     "name":"Peter",
//     "sibling":{
//         "age":20,
//         "name":"Anna",
//         "type":0
//     }
// }

p.BirthDate.Set(time.Unix(852073200, 0))
p.Books.Set([]string{"George Orwell - 1984", "Stephen E. Ambrose - Band of Brothers"})
p.ExamScores.Set(CustomMap[string, int64]{
    "math":    80,
    "physics": 90,
})

j, _ = json.Marshal(p)
log.Printf("%s", j)
// {
//     "age":25,
//     "birth_date":"1997-01-01T00:00:00+01:00",
//     "books":[
//         "George Orwell - 1984",
//         "Stephen E. Ambrose - Band of Brothers"
//     ],
//     "exam_scores":{
//         "math":80,
//         "physics":90
//     },
//     "name":"Peter",
//     "sibling":{
//         "age":20,
//         "name":"Anna",
//         "type":0
//     }
// }

p.Books.SetNil()     // will be null
p.ExamScores.Unset() // will be unset
p.Sibling.Unset()    // will be unset

j, _ = json.Marshal(p)
log.Printf("%s", j)
// {
//     "age":25,
//     "birth_date":"1997-01-01T00:00:00+01:00",
//     "books":null,
//     "name":"Peter"
// }
  1. Default JSON unmarshal
var p Person
log.Printf("%+v", p)
// {
//     Name:       {set:false valid:false value:} 
//     Age:        {set:false valid:false value:0} 
//     BirthDate:  {set:false valid:false value:{wall:0 ext:0 loc:<nil>}} 
//     Books:      {set:false valid:false value:[]} 
//     ExamScores: {set:false valid:false value:map[]} 
//     Sibling:    {set:false valid:false value:{
//             Name:   {set:false valid:false value:} 
//             Age:    {set:false valid:false value:0} 
//             Type:   {set:false valid:false value:0}
//         }
//     }
// }

jsonStr1 := []byte(`{}`)
_ = json.Unmarshal(jsonStr1, &p)
log.Printf("%+v", p)
// {
//     Name:       {set:false valid:false value:} 
//     Age:        {set:false valid:false value:0} 
//     BirthDate:  {set:false valid:false value:{wall:0 ext:0 loc:<nil>}} 
//     Books:      {set:false valid:false value:[]} 
//     ExamScores: {set:false valid:false value:map[]} 
//     Sibling:    {set:false valid:false value:{
//             Name:   {set:false valid:false value:} 
//             Age:    {set:false valid:false value:0} 
//             Type:   {set:false valid:false value:0}
//         }
//     }
// }

p = Person{} // reset variale
jsonStr2 := []byte(`{"age":25,"name":"Peter","sibling":{"age":20,"name":"Anna","type":0}}`)
_ = json.Unmarshal(jsonStr2, &p)
log.Printf("%+v", p)
// {
//     Name:       {set:true valid:true value:Peter} 
//     Age:        {set:true valid:true value:25} 
//     BirthDate:  {set:false valid:false value:{wall:0 ext:0 loc:<nil>}} 
//     Books:      {set:false valid:false value:[]} 
//     ExamScores: {set:false valid:false value:map[]} 
//     Sibling:    {set:true valid:true value:{
//             Name:   {set:true valid:true value:Anna} 
//             Age:    {set:true valid:true value:20} 
//             Type:   {set:true valid:true value:0}
//         }
//     }
// }


p = Person{} // reset variale
jsonStr3 := []byte(`{"age":25,"birth_date":"1997-01-01T00:00:00+01:00","books":["George Orwell - 1984","Stephen E. Ambrose - Band of Brothers"],"exam_scores":{"math":80,"physics":90},"name":"Peter","sibling":{"age":20,"name":"Anna","type":0}}`)
_ = json.Unmarshal(jsonStr3, &p)
log.Printf("%+v", p)
// {
//     Name:       {set:true valid:true value:Peter} 
//     Age:        {set:true valid:true value:25} 
//     BirthDate:  {set:true valid:true value:{wall:0 ext:62987670000 loc:0x2ca260}} 
//     Books:      {set:true valid:true value:[George Orwell - 1984 Stephen E. Ambrose - Band of Brothers]} 
//     ExamScores: {set:true valid:true value:map[math:80 physics:90]} 
//     Sibling:    {set:true valid:true value:{
//             Name:   {set:true valid:true value:Anna} 
//             Age:    {set:true valid:true value:20} 
//             Type:   {set:true valid:true value:0}
//         }
//     }
// }

p = Person{} // reset variale
jsonStr4 := []byte(`{"age":25,"birth_date":"1997-01-01T00:00:00+01:00","books":null,"name":"Peter"}`)
_ = json.Unmarshal(jsonStr4, &p)
log.Printf("%+v", p)
// {
//     Name:       {set:true valid:true value:Peter} 
//     Age:        {set:true valid:true value:25} 
//     BirthDate:  {set:true valid:true value:{wall:0 ext:62987670000 loc:0x2ca260}} 
//     Books:      {set:true valid:false value:[]} 
//     ExamScores: {set:false valid:false value:map[]} 
//     Sibling:    {set:false valid:false value:{
//             Name:   {set:false valid:false value:} 
//             Age:    {set:false valid:false value:0} 
//             Type:   {set:false valid:false value:0}
//         }
//     }
// }
  1. Implement JSON unmarshaler for SiblingType
// UnmarshalJSON implements the json.Unmarshaler interface
func (st *SiblingType) UnmarshalJSON(data []byte) error {
    // null value already handled by null.Var, 
    // so we don't need to check for that here

	str := string(data)
	if str == "brother" {
		*st = Brother
	} else if str == "sister" {
		*st = Sister
	} else {
		return fmt.Errorf("invalid type for %T", st)
	}

	return nil
}

var p Person
log.Printf("%+v", p)
// {
//     Name:       {set:false valid:false value:} 
//     Age:        {set:false valid:false value:0} 
//     BirthDate:  {set:false valid:false value:{wall:0 ext:0 loc:<nil>}} 
//     Books:      {set:false valid:false value:[]} 
//     ExamScores: {set:false valid:false value:map[]} 
//     Sibling:    {set:false valid:false value:{
//             Name:   {set:false valid:false value:} 
//             Age:    {set:false valid:false value:0} 
//             Type:   {set:false valid:false value:0}
//         }
//     }
// }

jsonStr1 := []byte(`{"age":25,"name":"Peter","sibling":{"age":20,"name":"Anna","type":"sister"}}`)
_ = json.Unmarshal(jsonStr1, &p)
log.Printf("%+v", p)
// {
//     Name:       {set:true valid:true value:Peter} 
//     Age:        {set:true valid:true value:25} 
//     BirthDate:  {set:false valid:false value:{wall:0 ext:0 loc:<nil>}} 
//     Books:      {set:false valid:false value:[]} 
//     ExamScores: {set:false valid:false value:map[]} 
//     Sibling:    {set:true valid:true value:{
//             Name:   {set:true valid:true value:Anna} 
//             Age:    {set:true valid:true value:20} 
//             Type:   {set:true valid:true value:0}
//         }
//     }
// }

p = Person{}
jsonStr2 := []byte(`{"age":25,"name":"Peter","sibling":{"age":20,"name":"Anna","type":"foo"}}`)
err := json.Unmarshal(jsonStr2, &p)
log.Printf("%v", err)
// invalid type for *SiblingType

p = Person{}
jsonStr3 := []byte(`{"age":25,"name":"Peter","sibling":null}`)
_ = json.Unmarshal(jsonStr3, &p)
log.Printf("%+v", p)
// {
//     Name:       {set:true valid:true value:Peter} 
//     Age:        {set:true valid:true value:25} 
//     BirthDate:  {set:false valid:false value:{wall:0 ext:0 loc:<nil>}} 
//     Books:      {set:false valid:false value:[]} 
//     ExamScores: {set:false valid:false value:map[]} 
//     Sibling:    {set:true valid:false value:{
//             Name:   {set:false valid:false value:} 
//             Age:    {set:false valid:false value:0} 
//             Type:   {set:false valid:false value:0}
//         }
//     }
// }
  1. Use with the database/sql package
// NOTE:
// If the underlying value is not compatible with the database/sql package by default, 
// then implement sql.Scanner and driver.Valuer interfaces for the underlying type

var p Person

// Exec
// If any of the variables are not set or set to NULL, then NULL will be inserted
_ = db.Exec(/* query */, p.Age, p.Name)

// Scan
// If any of the values scanned will be NULL, then the IsValid() will return false. 
// IsSet() will return true for every field used in the scan.
_ = db.QueryRow(/* query */, &p.Age, &p.Name)

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Var

type Var[T any] struct {
	// contains filtered or unexported fields
}

Var[T] defines a variable with generic field of T type

func (Var[T]) IsSet

func (v Var[T]) IsSet() bool

IsSet returns if the value was set

func (Var[T]) MarshalJSON

func (v Var[T]) MarshalJSON() ([]byte, error)

MarshalJSON implements the json.Marshaler interface

func (*Var[T]) Scan

func (v *Var[T]) Scan(src any) error

Scan implements the sql.Scanner interface

func (*Var[T]) Set

func (v *Var[T]) Set(value T)

Set sets the value

func (*Var[T]) SetNil

func (v *Var[T]) SetNil()

SetNil sets the value to NULL

func (*Var[T]) UnmarshalJSON

func (v *Var[T]) UnmarshalJSON(data []byte) error

UnmarshalJSON implements the json.Unmarshaler interface

func (*Var[T]) Unset

func (v *Var[T]) Unset()

Unset unsets the value

func (Var[T]) Val

func (v Var[T]) Val() T

Val returns the value

func (Var[T]) Valid

func (v Var[T]) Valid() bool

Valid returns if the value is NULL

func (Var[T]) Value

func (v Var[T]) Value() (driver.Value, error)

Value implements the sql package's driver.Valuer interface

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL