und

package module
v1.0.0-alpha3 Latest Latest
Warning

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

Go to latest
Published: Jul 7, 2024 License: Apache-2.0 Imports: 9 Imported by: 0

README

und - option and undefined-able types, mainly for JSON fields.

Types to interoperate with applications that make full use of JSON.

Before v1

  • und waits for release of encoding/json/v2
    • und depends on github.com/go-json-experiment/json which is an experimental encoding/json/v2 implementation.
    • Types defined in this module implement json.MarshalerV2 and json.UnmarshalerV2. The API dependency is relatively thin and narrow. I suspects they will not break the interface the part where we are relaying.
  • It'll eventually have a breaking change when encoding/json/v2 is released.
    • However that should not need change of your code, just bump version.

Example

run example by go run github.com/ngicks/und/[email protected].

You can skip fields by jsonv2(github.com/go-json-experiment/json) with omitzero json option.

Or you can skip sliceund and sliceund/elastic type fields with encoding/json v1.

package main

import (
	"encoding/json"
	"fmt"

	"github.com/ngicks/und"
	"github.com/ngicks/und/elastic"
	"github.com/ngicks/und/option"

	jsonv2 "github.com/go-json-experiment/json"
	"github.com/go-json-experiment/json/jsontext"
	"github.com/ngicks/und/sliceund"
	sliceelastic "github.com/ngicks/und/sliceund/elastic"
)

type sample1 struct {
	Foo  string
	Bar  und.Und[nested1]              `json:",omitzero"`
	Baz  elastic.Elastic[nested1]      `json:",omitzero"`
	Qux  sliceund.Und[nested1]         `json:",omitzero"`
	Quux sliceelastic.Elastic[nested1] `json:",omitzero"`
}

type nested1 struct {
	Bar  und.Und[string]            `json:",omitzero"`
	Baz  elastic.Elastic[int]       `json:",omitzero"`
	Qux  sliceund.Und[float64]      `json:",omitzero"`
	Quux sliceelastic.Elastic[bool] `json:",omitzero"`
}

type sample2 struct {
	Foo  string
	Bar  und.Und[nested2]              `json:",omitempty"`
	Baz  elastic.Elastic[nested2]      `json:",omitempty"`
	Qux  sliceund.Und[nested2]         `json:",omitempty"`
	Quux sliceelastic.Elastic[nested2] `json:",omitempty"`
}

type nested2 struct {
	Bar  und.Und[string]            `json:",omitempty"`
	Baz  elastic.Elastic[int]       `json:",omitempty"`
	Qux  sliceund.Und[float64]      `json:",omitempty"`
	Quux sliceelastic.Elastic[bool] `json:",omitempty"`
}

func main() {
	s1 := sample1{
		Foo:  "foo",
		Bar:  und.Defined(nested1{Bar: und.Defined("foo")}),
		Baz:  elastic.FromValue(nested1{Baz: elastic.FromOptions([]option.Option[int]{option.Some(5), option.None[int](), option.Some(67)})}),
		Qux:  sliceund.Defined(nested1{Qux: sliceund.Defined(float64(1.223))}),
		Quux: sliceelastic.FromValue(nested1{Quux: sliceelastic.FromOptions([]option.Option[bool]{option.None[bool](), option.Some(true), option.Some(false)})}),
	}

	var (
		bin []byte
		err error
	)
	bin, err = jsonv2.Marshal(s1, jsontext.WithIndent("    "))
	if err != nil {
		panic(err)
	}
	fmt.Printf("marshaled by v2=\n%s\n", bin)
	// see? undefined (=zero value) fields are skipped.
	/*
	   marshaled by v2=
	   {
	       "Foo": "foo",
	       "Bar": {
	           "Bar": "foo"
	       },
	       "Baz": [
	           {
	               "Baz": [
	                   5,
	                   null,
	                   67
	               ]
	           }
	       ],
	       "Qux": {
	           "Qux": 1.223
	       },
	       "Quux": [
	           {
	               "Quux": [
	                   null,
	                   true,
	                   false
	               ]
	           }
	       ]
	   }
	*/

	s2 := sample2{
		Foo:  "foo",
		Bar:  und.Defined(nested2{Bar: und.Defined("foo")}),
		Baz:  elastic.FromValue(nested2{Baz: elastic.FromOptions([]option.Option[int]{option.Some(5), option.None[int](), option.Some(67)})}),
		Qux:  sliceund.Defined(nested2{Qux: sliceund.Defined(float64(1.223))}),
		Quux: sliceelastic.FromValue(nested2{Quux: sliceelastic.FromOptions([]option.Option[bool]{option.None[bool](), option.Some(true), option.Some(false)})}),
	}

	bin, err = json.MarshalIndent(s2, "", "    ")
	if err != nil {
		panic(err)
	}
	fmt.Printf("marshaled by v1=\n%s\n", bin)
	// You see. Types defined under ./sliceund/ can be skipped by encoding/json.
	// Types defined in ./ and ./elastic cannot be skipped by it.
	/*
	   marshaled by v1=
	   	{
	   	    "Foo": "foo",
	   	    "Bar": {
	   	        "Bar": "foo",
	   	        "Baz": null
	   	    },
	   	    "Baz": [
	   	        {
	   	            "Bar": null,
	   	            "Baz": [
	   	                5,
	   	                null,
	   	                67
	   	            ]
	   	        }
	   	    ],
	   	    "Qux": {
	   	        "Bar": null,
	   	        "Baz": null,
	   	        "Qux": 1.223
	   	    },
	   	    "Quux": [
	   	        {
	   	            "Bar": null,
	   	            "Baz": null,
	   	            "Quux": [
	   	                null,
	   	                true,
	   	                false
	   	            ]
	   	        }
	   	    ]
	   	}
	*/
}

being undefined is harder to express in Go.

  • JSON, JavaScript Object Notation, includes concept of being absence(undefined), nil(null) or value(T).
  • When unmarshaling incoming JSON bytes slice, you can use *T to decode value matching T and additionally converts undefined OR null to nil.
    • nil indicates the JSON field was null and thus nil was assigned to the field.
    • Or the field was nil and json.Unmarshal skipped modifying the field since no matching JSON field was present in the input.
  • That's hard to express in Go, since:
    • You can always do that by encoding struct into map[string]any then remove fields that are supposed to be absent in an output JSON value.
    • Or define custom JSON marshaler that skips if fields are in some specific state.
    • If you do not want to do those,
      • You'll need a type that has 3 states.
      • You can do it by various ways, which include **T, []T, map[bool]T and struct {state int; value T}.
        • **T is definitely not a choice since it is not allowed to be a method receiver as per specification.
        • struct {state int; value T} can not be skipped by v1 encoding/json since it does not check emptiness of struct.

As a conclusion, this package implements struct {state int; value T} and []T kind types.

types and variants

  • Option[T]: Rust-like optional value.
    • can be Some or None.
    • is comparable if T is comparable.
    • have Equal method in case T is not comparable or comparable but needs custom equality tests(e.g. time.Time)
    • has convenient methods stolen from rust's option<T>
    • can be used in place of *T
    • is copied by assign.

Other types are based on Option[T].

  • Und[T]: undefined, null or T
  • Elastic[T]: undefined, null, T or [](T | null)
    • mainly for consuming elasticsearch JSON documents.
    • or configuration files.

There are 2 variants

  • github.com/ngicks/und: Option[Option[T]] based types.
    • skippable only if encoding through
      • github.com/go-json-experiment/json(possibly a future encoding/json/v2) with the ,omitzero options
      • jsoniter with ,omitempty and custom encoder.
  • github.com/ngicks/und/sliceund: []Option[T] based types.
    • skippable with ,omitempty.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type SqlNull

type SqlNull[T any] struct {
	Und[T]
}

SqlNull[T] adapts Und[T] to sql.Scanner and driver.Valuer.

func (*SqlNull[T]) Scan

func (n *SqlNull[T]) Scan(src any) error

Scan implements sql.Scanner.

If T or *T implements sql.Scanner, the implementation is used. Otherwise, SqlNull[T] falls back to sql.Null[T] as sql.Scanner.

func (SqlNull[T]) Value

func (n SqlNull[T]) Value() (driver.Value, error)

Value implements driver.Valuer.

If T or *T implements driver.Valuer, the implementation is used. In this respect, T should not be a pointer type or Und[T] should not store nil value. Otherwise, SqlNull[T] falls back to sql.Null[T] as driver.Valuer.

type Und

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

Und[T] is a type that can express a value (`T`), empty (`null`), or absent (`undefined`). Und[T] is comparable if T is comparable. And it can be copied by assign.

Und[T] implements IsZero and can be skippable struct fields when marshaled through appropriate marshalers, e.g. "github.com/go-json-experiment/json/jsontext" with omitzero option set to the field, or "github.com/json-iterator/go" with omitempty option to the field and an appropriate extension.

If you need to stick with encoding/json v1, you can use github.com/ngicks/und/sliceund, a slice based version of Und[T] whish is already skppable by v1.

func Defined

func Defined[T any](t T) Und[T]

Defined returns a defined Und[T] whose internal value is t.

func FromOption

func FromOption[T any](opt option.Option[option.Option[T]]) Und[T]

FromOptions converts opt into an Und[T]. opt is retained by the returned value.

func FromPointer

func FromPointer[T any](v *T) Und[T]

FromPointer converts *T into Und[T]. If v is nil, it returns an undefined Und. Otherwise, it returns Defined[T] whose value is the dereferenced v.

func FromSqlNull

func FromSqlNull[T any](v sql.Null[T]) Und[T]

FromSqlNull converts a valid sql.Null[T] to a defined Und[T] and invalid one into a null Und[].

func Null

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

Null returns a null Und[T].

func Undefined

func Undefined[T any]() Und[T]

Undefined returns an undefined Und[T].

func (Und[T]) CheckUnd

func (u Und[T]) CheckUnd() error

func (Und[T]) Clone

func (u Und[T]) Clone() Und[T]

Clone clones u. This is only a copy-by-assign unless T implements Cloner[T].

func (Und[T]) DoublePointer

func (u Und[T]) DoublePointer() **T

DoublePointer returns nil if u is undefined, &(*T)(nil) if null, the internal value if defined.

func (Und[T]) Equal

func (u Und[T]) Equal(other Und[T]) bool

Equal implements Equality[Und[T]]. Equal panics if T is uncomparable and does not implement Equality[T].

func (Und[T]) IsDefined

func (u Und[T]) IsDefined() bool

IsDefined returns true if u is a defined value, otherwise false.

func (Und[T]) IsNull

func (u Und[T]) IsNull() bool

IsNull returns true if u is a null value, otherwise false.

func (Und[T]) IsUndefined

func (u Und[T]) IsUndefined() bool

IsUndefined returns true if u is an undefined value, otherwise false.

func (Und[T]) IsZero

func (u Und[T]) IsZero() bool

IsZero is an alias for IsUndefined.

func (Und[T]) LogValue

func (u Und[T]) LogValue() slog.Value

LogValue implements slog.LogValuer.

func (Und[T]) Map

func (u Und[T]) Map(f func(option.Option[option.Option[T]]) option.Option[option.Option[T]]) Und[T]

Map returns a new Und[T] whose internal value is u's mapped by f.

func (Und[T]) MarshalJSON

func (u Und[T]) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler.

func (Und[T]) MarshalJSONV2

func (u Und[T]) MarshalJSONV2(enc *jsontext.Encoder, opts jsonv2.Options) error

MarshalJSONV2 implements jsonv2.MarshalerV2.

func (Und[T]) MarshalXML

func (o Und[T]) MarshalXML(e *xml.Encoder, start xml.StartElement) error

MarshalXML implements xml.Marshaler.

func (Und[T]) Pointer

func (u Und[T]) Pointer() *T

Pointer returns u's internal value as a pointer.

func (Und[T]) SqlNull

func (u Und[T]) SqlNull() sql.Null[T]

SqlNull converts o into sql.Null[T].

func (*Und[T]) UnmarshalJSON

func (u *Und[T]) UnmarshalJSON(data []byte) error

UnmarshalJSON implements json.Unmarshaler.

func (*Und[T]) UnmarshalJSONV2

func (u *Und[T]) UnmarshalJSONV2(dec *jsontext.Decoder, opts jsonv2.Options) error

UnmarshalJSONV2 implements jsonv2.UnmarshalerV2.

func (*Und[T]) UnmarshalXML

func (o *Und[T]) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error

UnmarshalXML implements xml.Unmarshaler.

func (Und[T]) Unwrap

func (u Und[T]) Unwrap() option.Option[option.Option[T]]

Unwrap returns u's internal value.

func (Und[T]) ValidateUnd

func (u Und[T]) ValidateUnd() error

func (Und[T]) Value

func (u Und[T]) Value() T

Value returns an internal value.

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

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