Types to interoperate with applications that make full use of JSON.
- und waits for release of
encoding/json/v2
- und depends on
github.com/go-json-experiment/json
which is an experimentalencoding/json/v2
implementation. - Types defined in this module implement
json.MarshalerV2
andjson.UnmarshalerV2
. The API dependency is relatively thin and narrow. I suspects they will not break the interface the part where we are relaying.
- und depends on
- 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.
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
]
}
]
}
*/
}
- 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 matchingT
and additionally converts undefined OR null to nil.nil
indicates the JSON field wasnull
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
andstruct {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 v1encoding/json
since it does not check emptiness of struct.
- You can always do that by encoding struct into
As a conclusion, this package implements struct {state int; value T}
and []T
kind types.
Option[T]
: Rust-like optional value.- can be Some or None.
- is comparable if
T
is comparable. - have
Equal
method in caseT
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 orT
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 futureencoding/json/v2
) with the,omitzero
options- jsoniter with
,omitempty
and custom encoder.
- skippable only if encoding through
github.com/ngicks/und/sliceund
:[]Option[T]
based types.- skippable with
,omitempty
.
- skippable with