null

package module
v0.2.1 Latest Latest
Warning

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

Go to latest
Published: Jul 4, 2025 License: MIT Imports: 2 Imported by: 0

README

null

A Go Nullable type for JSON APIs, handling three states: valid, null, or unset. Ideal for PATCH requests and optional fields.

Why?

Go's encoding/json struggles to distinguish absent fields from explicit null in JSON. Existing solutions like sql.NullString or guregu/null are two-state and fail with omitempty. This library uses a generic map[bool]T to cleanly handle all three states.

Features

  • Tri-State: Differentiates valid, null, and unset.
  • Type-Safe: Generic support for any type ([T any]).
  • Idiomatic: Simple API, seamless with omitempty.
  • Self-Contained: No custom struct methods needed.

Installation

go get github.com/qntx/null

Example

Struct
type UserPayload struct {
    Name null.Nullable[string] `json:"name,omitempty"`
    Age  null.Nullable[int]    `json:"age,omitempty"`
    Bio  null.Nullable[string] `json:"bio"`
}
Marshaling
payload := UserPayload{
    Name: null.From("Alice"),  // Valid
    Age:  null.Null[int](),    // Null
    Bio:  null.Zero[string](), // Unset
}

data, _ := json.Marshal(payload)
fmt.Println(string(data)) // {"name":"Alice","age":null,"bio":null}

payload2 := UserPayload{
    Name: null.From("Bob"),
    Bio:  null.Null[string](),
}

data, _ = json.Marshal(payload2)
fmt.Println(string(data)) // {"name":"Bob","bio":null}

Performance

Nullable is ~3x slower than pointers for marshaling and ~2x for unmarshaling due to its map internals. For most APIs, this nanosecond overhead is negligible compared to the clarity and correctness gained.

The following benchmarks were run on a 13th Gen Intel(R) Core(TM) i7-13700H CPU.

Benchmark Operations Time/Op Memory/Op Allocations/Op
Marshal (This Library) 3,598,364 336.1 ns/op 72 B/op 5 allocs/op
Marshal (Native Pointers) 10,764,397 112.0 ns/op 48 B/op 2 allocs/op
Unmarshal (This Library) 1,708,104 707.7 ns/op 816 B/op 10 allocs/op
Unmarshal (Native Pointers) 3,508,210 337.1 ns/op 216 B/op 4 allocs/op

License

MIT

Acknowledgements

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type JSONNullable added in v0.2.1

type JSONNullable interface {
	IsNull() bool
	SetNull()
	IsSet() bool
	Reset()
}

JSONNullable defines the interface for types that can represent JSON nullability states.

type Nullable

type Nullable[T any] map[bool]T

Nullable is a generic type that implements a field with three possible states: - field is not set in the request - field is explicitly set to `null` in the request - field is explicitly set to a valid value in the request

func From added in v0.2.1

func From[T any](value T) Nullable[T]

From constructs a Nullable[T] with the given value, representing a field explicitly set in a JSON request.

Example
package main

import (
	"fmt"

	"github.com/qntx/null"
)

func main() {
	p := struct {
		N null.Nullable[int]
	}{}

	p.N = null.From(123)

	fmt.Println(p.N.Get())
}
Output:

123 true

func Null

func Null[T any]() Nullable[T]

Null constructs a Nullable[T] with an explicit `null`, representing a field set to `null` in a JSON request.

Example
package main

import (
	"fmt"

	"github.com/qntx/null"
)

func main() {
	p := struct {
		N null.Nullable[int]
	}{}

	p.N = null.Null[int]()

	fmt.Printf("Specified: %v\n", p.N.IsSet())
	fmt.Printf("Nullable: %v\n", p.N.IsNull())
}
Output:

Specified: true
Nullable: true
Example (MarshalOptional)
package main

import (
	"encoding/json"
	"fmt"

	"github.com/qntx/null"
)

func main() {
	obj := struct {
		ID null.Nullable[int] `json:"id,omitempty"`
	}{}

	// when it's not set (by default)
	b, err := json.Marshal(obj)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}
	fmt.Println("Unspecified:")
	fmt.Printf(`JSON: %s`+"\n", b)
	fmt.Println("---")

	// when it's not set (explicitly)
	obj.ID.Reset()

	b, err = json.Marshal(obj)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}
	fmt.Println("Unspecified:")
	fmt.Printf(`JSON: %s`+"\n", b)
	fmt.Println("---")

	// when it's set explicitly to nil
	obj.ID.SetNull()

	b, err = json.Marshal(obj)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}
	fmt.Println("Nullable:")
	fmt.Printf(`JSON: %s`+"\n", b)
	fmt.Println("---")

	// when it's set explicitly to the zero value
	var v int
	obj.ID.Set(v)

	b, err = json.Marshal(obj)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}
	fmt.Println("Zero value:")
	fmt.Printf(`JSON: %s`+"\n", b)
	fmt.Println("---")

	// when it's set explicitly to a specific value
	v = 12345
	obj.ID.Set(v)

	b, err = json.Marshal(obj)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}
	fmt.Println("Value:")
	fmt.Printf(`JSON: %s`+"\n", b)
	fmt.Println("---")

}
Output:

Unspecified:
JSON: {}
---
Unspecified:
JSON: {}
---
Nullable:
JSON: {"id":null}
---
Zero value:
JSON: {"id":0}
---
Value:
JSON: {"id":12345}
---
Example (MarshalRequired)
package main

import (
	"encoding/json"
	"fmt"

	"github.com/qntx/null"
)

func main() {
	obj := struct {
		ID null.Nullable[int] `json:"id"`
	}{}

	// when it's not set (by default)
	b, err := json.Marshal(obj)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}
	fmt.Println("Unspecified:")
	fmt.Printf(`JSON: %s`+"\n", b)
	fmt.Println("---")

	// when it's not set (explicitly)
	obj.ID.Reset()

	b, err = json.Marshal(obj)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}
	fmt.Println("Unspecified:")
	fmt.Printf(`JSON: %s`+"\n", b)
	fmt.Println("---")

	// when it's set explicitly to nil
	obj.ID.SetNull()

	b, err = json.Marshal(obj)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}
	fmt.Println("Nullable:")
	fmt.Printf(`JSON: %s`+"\n", b)
	fmt.Println("---")

	// when it's set explicitly to the zero value
	var v int
	obj.ID.Set(v)

	b, err = json.Marshal(obj)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}
	fmt.Println("Zero value:")
	fmt.Printf(`JSON: %s`+"\n", b)
	fmt.Println("---")

	// when it's set explicitly to a specific value
	v = 12345
	obj.ID.Set(v)

	b, err = json.Marshal(obj)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}
	fmt.Println("Value:")
	fmt.Printf(`JSON: %s`+"\n", b)
	fmt.Println("---")

}
Output:

Unspecified:
JSON: {"id":0}
---
Unspecified:
JSON: {"id":0}
---
Nullable:
JSON: {"id":null}
---
Zero value:
JSON: {"id":0}
---
Value:
JSON: {"id":12345}
---
Example (UnmarshalOptional)
package main

import (
	"encoding/json"
	"fmt"

	"github.com/qntx/null"
)

func main() {
	obj := struct {
		// Note that there is no pointer for null.Nullable when it's
		Name null.Nullable[string] `json:"name,omitempty"`
	}{}

	// when it's not set
	err := json.Unmarshal([]byte(`
		{
		}
		`), &obj)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}
	fmt.Println("Unspecified:")
	fmt.Printf("obj.Name.IsSet(): %v\n", obj.Name.IsSet())
	fmt.Printf("obj.Name.IsNull(): %v\n", obj.Name.IsNull())
	fmt.Println("---")

	// when it's set explicitly to nil
	err = json.Unmarshal([]byte(`
		{
		"name": null
		}
		`), &obj)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}
	fmt.Println("Nullable:")
	fmt.Printf("obj.Name.IsSet(): %v\n", obj.Name.IsSet())
	fmt.Printf("obj.Name.IsNull(): %v\n", obj.Name.IsNull())
	fmt.Println("---")

	// when it's set explicitly to the zero value
	err = json.Unmarshal([]byte(`
		{
		"name": ""
		}
		`), &obj)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}
	fmt.Println("Zero value:")
	fmt.Printf("obj.Name.IsSet(): %v\n", obj.Name.IsSet())
	fmt.Printf("obj.Name.IsNull(): %v\n", obj.Name.IsNull())
	val, ok := obj.Name.Get()
	if !ok {
		fmt.Printf("Error: %v\n", "value is not specified or null")
		return
	}
	fmt.Printf("obj.Name.Get(): %#v <nil>\n", val)
	fmt.Printf("obj.Name.MustGet(): %#v\n", obj.Name.MustGet())
	fmt.Println("---")

	// when it's set explicitly to a specific value
	err = json.Unmarshal([]byte(`
		{
		"name": "foo"
		}
		`), &obj)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Println("Value:")
	fmt.Printf("obj.Name.IsSet(): %v\n", obj.Name.IsSet())
	fmt.Printf("obj.Name.IsNull(): %v\n", obj.Name.IsNull())
	val, ok = obj.Name.Get()
	if !ok {
		fmt.Printf("Error: %v\n", "value is not specified or null")
		return
	}
	fmt.Printf("obj.Name.Get(): %#v <nil>\n", val)
	fmt.Printf("obj.Name.MustGet(): %#v\n", obj.Name.MustGet())
	fmt.Println("---")

}
Output:

Unspecified:
obj.Name.IsSet(): false
obj.Name.IsNull(): false
---
Nullable:
obj.Name.IsSet(): true
obj.Name.IsNull(): true
---
Zero value:
obj.Name.IsSet(): true
obj.Name.IsNull(): false
obj.Name.Get(): "" <nil>
obj.Name.MustGet(): ""
---
Value:
obj.Name.IsSet(): true
obj.Name.IsNull(): false
obj.Name.Get(): "foo" <nil>
obj.Name.MustGet(): "foo"
---
Example (UnmarshalRequired)
package main

import (
	"encoding/json"
	"fmt"

	"github.com/qntx/null"
)

func main() {
	obj := struct {
		Name null.Nullable[string] `json:"name"`
	}{}

	// when it's not set
	err := json.Unmarshal([]byte(`
		{
		}
		`), &obj)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}
	fmt.Println("Unspecified:")
	fmt.Printf("obj.Name.IsSet(): %v\n", obj.Name.IsSet())
	fmt.Printf("obj.Name.IsNull(): %v\n", obj.Name.IsNull())
	fmt.Println("---")

	// when it's set explicitly to nil
	err = json.Unmarshal([]byte(`
		{
		"name": null
		}
		`), &obj)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}
	fmt.Println("Nullable:")
	fmt.Printf("obj.Name.IsSet(): %v\n", obj.Name.IsSet())
	fmt.Printf("obj.Name.IsNull(): %v\n", obj.Name.IsNull())
	fmt.Println("---")

	// when it's set explicitly to the zero value
	err = json.Unmarshal([]byte(`
		{
		"name": ""
		}
		`), &obj)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}
	fmt.Println("Zero value:")
	fmt.Printf("obj.Name.IsSet(): %v\n", obj.Name.IsSet())
	fmt.Printf("obj.Name.IsNull(): %v\n", obj.Name.IsNull())
	val, ok := obj.Name.Get()
	if !ok {
		fmt.Printf("Error: %v\n", "value is not specified or null")
		return
	}
	fmt.Printf("obj.Name.Get(): %#v <nil>\n", val)
	fmt.Printf("obj.Name.MustGet(): %#v\n", obj.Name.MustGet())
	fmt.Println("---")

	// when it's set explicitly to a specific value
	err = json.Unmarshal([]byte(`
		{
		"name": "foo"
		}
		`), &obj)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Println("Value:")
	fmt.Printf("obj.Name.IsSet(): %v\n", obj.Name.IsSet())
	fmt.Printf("obj.Name.IsNull(): %v\n", obj.Name.IsNull())
	val, ok = obj.Name.Get()
	if !ok {
		fmt.Printf("Error: %v\n", "value is not specified or null")
		return
	}
	fmt.Printf("obj.Name.Get(): %#v <nil>\n", val)
	fmt.Printf("obj.Name.MustGet(): %#v\n", obj.Name.MustGet())
	fmt.Println("---")

}
Output:

Unspecified:
obj.Name.IsSet(): false
obj.Name.IsNull(): false
---
Nullable:
obj.Name.IsSet(): true
obj.Name.IsNull(): true
---
Zero value:
obj.Name.IsSet(): true
obj.Name.IsNull(): false
obj.Name.Get(): "" <nil>
obj.Name.MustGet(): ""
---
Value:
obj.Name.IsSet(): true
obj.Name.IsNull(): false
obj.Name.Get(): "foo" <nil>
obj.Name.MustGet(): "foo"
---

func Zero added in v0.2.1

func Zero[T any]() Nullable[T]

Zero constructs a Nullable[T] in the unset state, representing a field not provided in a JSON request.

func (Nullable[T]) Get

func (t Nullable[T]) Get() (T, bool)

Get retrieves the underlying value, if present, and returns an empty value and `false` if not present.

func (Nullable[T]) IsNull

func (t Nullable[T]) IsNull() bool

IsNull indicates whether the field was sent and had a value of `null`.

func (Nullable[T]) IsSet added in v0.2.1

func (t Nullable[T]) IsSet() bool

IsSet indicates whether the field was sent (either as null or a value).

func (Nullable[T]) MarshalJSON

func (t Nullable[T]) MarshalJSON() ([]byte, error)

func (Nullable[T]) MustGet

func (t Nullable[T]) MustGet() T

MustGet retrieves the underlying value, if present, and panics if not present.

func (*Nullable[T]) Reset added in v0.2.1

func (t *Nullable[T]) Reset()

Reset clears the field, making it unset.

func (*Nullable[T]) Set

func (t *Nullable[T]) Set(value T)

Set sets the underlying value to a given value.

func (*Nullable[T]) SetNull

func (t *Nullable[T]) SetNull()

SetNull sets the field to an explicit `null`.

func (*Nullable[T]) UnmarshalJSON

func (t *Nullable[T]) UnmarshalJSON(data []byte) error

Jump to

Keyboard shortcuts

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