param

package module
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Sep 3, 2025 License: MIT Imports: 3 Imported by: 0

README

param

A Go Opt 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/param

Example

Struct
type UserPayload struct {
    Name param.Opt[string] `json:"name,omitempty"`
    Age  param.Opt[int]    `json:"age,omitempty"`
    Bio  param.Opt[string] `json:"bio"`
}
Marshaling
payload := UserPayload{
    Name: param.String("Alice"),  // Valid
    Age:  param.Int(123),         // Valid
    Bio:  param.Zero[string](),   // Unset
}

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

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

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

Usage Guide

This guide helps you select the appropriate function based on your desired JSON output. The table below maps each field state to its corresponding function and behavior.

Intent Function Call JSON with omitempty JSON without omitempty Common Use Case
Set a Value param.From("Alice") "field": "Alice" "field": "Alice" Create/Update: Assign a specific, non-null value.
Set to null param.Null[string]() "field": null "field": null Clear: Explicitly nullify a field's value on the server.
Unset / Omit param.Zero[string]() Field is omitted "field": "" (zero-value) Partial Update (PATCH): Leave a field untouched during an update.

Key Points:

  • param.From(): Use to assign a specific, non-null value to a field.
  • param.Null(): Use to explicitly set a field to null.
  • param.Zero(): Ideal for partial updates (PATCH). When combined with an omitempty tag, the field is excluded from the JSON output, leaving the server-side value unchanged.
  • Important: Without omitempty, param.Zero() marshals to the type's zero-value (e.g., "" for string, 0 for int).

Performance

Opt 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

func Ptr added in v0.4.0

func Ptr[T any](v T) *T

Ptr returns a pointer to the given value.

Types

type JSONOpt

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

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

type Opt

type Opt[T any] map[bool]T

Opt 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 Bool added in v0.4.0

func Bool(b bool) Opt[bool]

func Float added in v0.4.0

func Float(f float64) Opt[float64]

func Float32 added in v0.4.0

func Float32(f float32) Opt[float32]

func From

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

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

Example
package main

import (
	"fmt"

	"github.com/qntx/param"
)

func main() {
	p := struct {
		N param.Opt[int]
	}{}

	p.N = param.Int(123)

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

123 true

func Int added in v0.4.0

func Int(i int) Opt[int]

func Int64 added in v0.4.0

func Int64(i int64) Opt[int64]

func Null

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

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

Example
package main

import (
	"fmt"

	"github.com/qntx/param"
)

func main() {
	p := struct {
		N param.Opt[int]
	}{}

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

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

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

import (
	"encoding/json"
	"fmt"

	"github.com/qntx/param"
)

func main() {
	obj := struct {
		ID param.Opt[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("Opt:")
	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: {}
---
Opt:
JSON: {"id":null}
---
Zero value:
JSON: {"id":0}
---
Value:
JSON: {"id":12345}
---
Example (MarshalRequired)
package main

import (
	"encoding/json"
	"fmt"

	"github.com/qntx/param"
)

func main() {
	obj := struct {
		ID param.Opt[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("Opt:")
	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}
---
Opt:
JSON: {"id":null}
---
Zero value:
JSON: {"id":0}
---
Value:
JSON: {"id":12345}
---
Example (UnmarshalOptional)
package main

import (
	"encoding/json"
	"fmt"

	"github.com/qntx/param"
)

func main() {
	obj := struct {
		// Note that there is no pointer for null.Opt when it's
		Name param.Opt[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("Opt:")
	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
---
Opt:
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/param"
)

func main() {
	obj := struct {
		Name param.Opt[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("Opt:")
	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
---
Opt:
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 String added in v0.4.0

func String(s string) Opt[string]

Helper functions for common types.

func Time added in v0.4.0

func Time(t time.Time) Opt[time.Time]

func Zero

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

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

func (Opt[T]) Get

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

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

func (Opt[T]) IsNull

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

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

func (Opt[T]) IsSet

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

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

func (Opt[T]) MarshalJSON

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

func (Opt[T]) MustGet

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

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

func (*Opt[T]) Reset

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

Reset clears the field, making it unset.

func (*Opt[T]) Set

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

Set sets the underlying value to a given value.

func (*Opt[T]) SetNull

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

SetNull sets the field to an explicit `null`.

func (*Opt[T]) UnmarshalJSON

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

Jump to

Keyboard shortcuts

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