nullable

package module
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: Jan 25, 2024 License: Apache-2.0 Imports: 3 Imported by: 4

README

oapi-codegen/nullable

An implementation of a Nullable type for JSON bodies, indicating whether the field is absent, set to null, or set to a value

Unlike other known implementations, this makes it possible to both marshal and unmarshal the value, as well as represent all three states:

  • the field is not set
  • the field is explicitly set to null
  • the field is explicitly set to a given value

And can be embedded in structs, for instance with the following definition:

obj := struct {
		// RequiredID is a required, nullable field
		RequiredID     nullable.Nullable[int]     `json:"id"`
		// OptionalString is an optional, nullable field
		// NOTE that no pointer is required, only `omitempty`
		OptionalString nullable.Nullable[string] `json:"optionalString,omitempty"`
}{}

Usage

[!IMPORTANT] Although this project is under the oapi-codegen org for the oapi-codegen OpenAPI-to-Go code generator, this is intentionally released as a separate, standalone library which can be used by other projects.

First, add to your project with:

go get github.com/oapi-codegen/nullable

Check out the examples in the package documentation on pkg.go.dev for more details.

Credits

As well as contributions from:

License

Licensed under the Apache-2.0 license.

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Nullable

type Nullable[T any] map[bool]T

Nullable is a generic type, which implements a field that can be one of three 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

Nullable is intended to be used with JSON marshalling and unmarshalling.

Internal implementation details:

- map[true]T means a value was provided - map[false]T means an explicit null was provided - nil or zero map means the field was not provided

If the field is expected to be optional, add the `omitempty` JSON tags. Do NOT use `*Nullable`!

Adapted from https://github.com/golang/go/issues/64515#issuecomment-1841057182

Example (MarshalOptional)
package main

import (
	"encoding/json"
	"fmt"

	"github.com/oapi-codegen/nullable"
)

func main() {
	obj := struct {
		ID nullable.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.SetUnspecified()

	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("Null:")
	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: {}
---
Null:
JSON: {"id":null}
---
Zero value:
JSON: {"id":0}
---
Value:
JSON: {"id":12345}
---
Example (MarshalRequired)
package main

import (
	"encoding/json"
	"fmt"

	"github.com/oapi-codegen/nullable"
)

func main() {
	obj := struct {
		ID nullable.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.SetUnspecified()

	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("Null:")
	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}
---
Null:
JSON: {"id":null}
---
Zero value:
JSON: {"id":0}
---
Value:
JSON: {"id":12345}
---
Example (UnmarshalOptional)
package main

import (
	"encoding/json"
	"fmt"

	"github.com/oapi-codegen/nullable"
)

func main() {
	obj := struct {
		// Note that there is no pointer for nullable.Nullable when it's
		Name nullable.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.IsSpecified(): %v\n", obj.Name.IsSpecified())
	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("Null:")
	fmt.Printf("obj.Name.IsSpecified(): %v\n", obj.Name.IsSpecified())
	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.IsSpecified(): %v\n", obj.Name.IsSpecified())
	fmt.Printf("obj.Name.IsNull(): %v\n", obj.Name.IsNull())
	val, err := obj.Name.Get()
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		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.IsSpecified(): %v\n", obj.Name.IsSpecified())
	fmt.Printf("obj.Name.IsNull(): %v\n", obj.Name.IsNull())
	val, err = obj.Name.Get()
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		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.IsSpecified(): false
obj.Name.IsNull(): false
---
Null:
obj.Name.IsSpecified(): true
obj.Name.IsNull(): true
---
Zero value:
obj.Name.IsSpecified(): true
obj.Name.IsNull(): false
obj.Name.Get(): "" <nil>
obj.Name.MustGet(): ""
---
Value:
obj.Name.IsSpecified(): true
obj.Name.IsNull(): false
obj.Name.Get(): "foo" <nil>
obj.Name.MustGet(): "foo"
---
Example (UnmarshalRequired)
package main

import (
	"encoding/json"
	"fmt"

	"github.com/oapi-codegen/nullable"
)

func main() {
	obj := struct {
		Name nullable.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.IsSpecified(): %v\n", obj.Name.IsSpecified())
	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("Null:")
	fmt.Printf("obj.Name.IsSpecified(): %v\n", obj.Name.IsSpecified())
	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.IsSpecified(): %v\n", obj.Name.IsSpecified())
	fmt.Printf("obj.Name.IsNull(): %v\n", obj.Name.IsNull())
	val, err := obj.Name.Get()
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		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.IsSpecified(): %v\n", obj.Name.IsSpecified())
	fmt.Printf("obj.Name.IsNull(): %v\n", obj.Name.IsNull())
	val, err = obj.Name.Get()
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		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.IsSpecified(): false
obj.Name.IsNull(): false
---
Null:
obj.Name.IsSpecified(): true
obj.Name.IsNull(): true
---
Zero value:
obj.Name.IsSpecified(): true
obj.Name.IsNull(): false
obj.Name.Get(): "" <nil>
obj.Name.MustGet(): ""
---
Value:
obj.Name.IsSpecified(): true
obj.Name.IsNull(): false
obj.Name.Get(): "foo" <nil>
obj.Name.MustGet(): "foo"
---

func NewNullNullable

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

NewNullNullable is a convenience helper to allow constructing a `Nullable` with an explicit `null`, for instance to construct a field inside a struct, without introducing an intermediate variable

Example
package main

import (
	"fmt"

	"github.com/oapi-codegen/nullable"
)

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

	p.N = nullable.NewNullNullable[int]()

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

Specified: true
Null: true

func NewNullableWithValue

func NewNullableWithValue[T any](t T) Nullable[T]

NewNullableWithValue is a convenience helper to allow constructing a `Nullable` with a given value, for instance to construct a field inside a struct, without introducing an intermediate variable

Example
package main

import (
	"fmt"

	"github.com/oapi-codegen/nullable"
)

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

	p.N = nullable.NewNullableWithValue(123)

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

123 <nil>

func (Nullable[T]) Get

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

Get retrieves the underlying value, if present, and returns an error if the value was not present

func (Nullable[T]) IsNull

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

IsNull indicate whether the field was sent, and had a value of `null`

func (Nullable[T]) IsSpecified

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

IsSpecified indicates whether the field was sent

func (Nullable[T]) MarshalJSON

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

func (Nullable[T]) MustGet added in v1.1.0

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

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

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 indicate that the field was sent, and had a value of `null`

func (*Nullable[T]) SetUnspecified

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

SetUnspecified indicate whether the field was sent

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