Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

serialise empty values as null (JSON) or empty element (XML) instead of the zero value of the type #3

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
135 changes: 98 additions & 37 deletions example_test.go
Expand Up @@ -4,8 +4,11 @@ import (
"encoding/json"
"encoding/xml"
"fmt"
"testing"
"time"

"github.com/stretchr/testify/assert"

"4d63.com/optional"
)

Expand Down Expand Up @@ -197,22 +200,22 @@ func Example_jsonMarshalEmpty() {

// Output:
// {
// "bool": false,
// "byte": 0,
// "float32": 0,
// "float64": 0,
// "int16": 0,
// "int32": 0,
// "int64": 0,
// "int": 0,
// "rune": 0,
// "string": "",
// "time": "0001-01-01T00:00:00Z",
// "uint16": 0,
// "uint32": 0,
// "uint64": 0,
// "uint": 0,
// "uintptr": 0
// "bool": null,
// "byte": null,
// "float32": null,
// "float64": null,
// "int16": null,
// "int32": null,
// "int64": null,
// "int": null,
// "rune": null,
// "string": null,
// "time": null,
// "uint16": null,
// "uint32": null,
// "uint64": null,
// "uint": null,
// "uintptr": null
// }
}

Expand Down Expand Up @@ -277,7 +280,7 @@ func Example_jsonMarshalPresent() {
// }
}

func Example_jsonUnmarshalEmpty() {
func TestExample_jsonUnmarshalEmpty(t *testing.T) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we keep this as an example? Examples already assert on the output.

s := struct {
Bool optional.Optional[bool] `json:"bool"`
Byte optional.Optional[byte] `json:"byte"`
Expand Down Expand Up @@ -335,6 +338,64 @@ func Example_jsonUnmarshalEmpty() {
// Uint64: false
// Uint: false
// Uintptr: false

t.Run("unmarshal null values to empty", func(t *testing.T) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you put this in a new example? Not a test please. The examples show up in docs and so help people understand how the library works.

s := struct {
Bool optional.Optional[bool] `json:"bool"`
Byte optional.Optional[byte] `json:"byte"`
Float32 optional.Optional[float32] `json:"float32"`
Float64 optional.Optional[float64] `json:"float64"`
Int16 optional.Optional[int16] `json:"int16"`
Int32 optional.Optional[int32] `json:"int32"`
Int64 optional.Optional[int64] `json:"int64"`
Int optional.Optional[int] `json:"int"`
Rune optional.Optional[rune] `json:"rune"`
String optional.Optional[string] `json:"string"`
Time optional.Optional[time.Time] `json:"time"`
Uint16 optional.Optional[uint16] `json:"uint16"`
Uint32 optional.Optional[uint32] `json:"uint32"`
Uint64 optional.Optional[uint64] `json:"uint64"`
Uint optional.Optional[uint] `json:"uint"`
Uintptr optional.Optional[uintptr] `json:"uintptr"`
}{}

x := `{
"float64": null,
"bool": null,
"byte": null,
"float32": null,
"int16": null,
"int32": null,
"int64": null,
"int": null,
"rune": null,
"string": null,
"time": null,
"uint16": null,
"uint32": null,
"uint64": null,
"uint": null,
"uintptr": null
}`
json.Unmarshal([]byte(x), &s)
assert.False(t, s.Bool.IsPresent())
assert.False(t, s.Byte.IsPresent())
assert.False(t, s.Float32.IsPresent())
assert.False(t, s.Float64.IsPresent())
assert.False(t, s.Int16.IsPresent())
assert.False(t, s.Int32.IsPresent())
assert.False(t, s.Int64.IsPresent())
assert.False(t, s.Int.IsPresent())
assert.False(t, s.Rune.IsPresent())
assert.False(t, s.String.IsPresent())
assert.False(t, s.Time.IsPresent())
assert.False(t, s.Uint16.IsPresent())
assert.False(t, s.Uint32.IsPresent())
assert.False(t, s.Uint64.IsPresent())
assert.False(t, s.Uint64.IsPresent())
assert.False(t, s.Uint.IsPresent())
assert.False(t, s.Uint.IsPresent())
})
}

func Example_jsonUnmarshalPresent() {
Expand Down Expand Up @@ -416,7 +477,7 @@ func Example_jsonUnmarshalPresent() {

func Example_xmlMarshalOmitEmpty() {
s := struct {
XMLName xml.Name `xml:"s"`
XMLName xml.Name `xml:"s"`
Bool optional.Optional[bool] `xml:"bool,omitempty"`
Byte optional.Optional[byte] `xml:"byte,omitempty"`
Float32 optional.Optional[float32] `xml:"float32,omitempty"`
Expand Down Expand Up @@ -461,7 +522,7 @@ func Example_xmlMarshalOmitEmpty() {

func Example_xmlMarshalEmpty() {
s := struct {
XMLName xml.Name `xml:"s"`
XMLName xml.Name `xml:"s"`
Bool optional.Optional[bool] `xml:"bool"`
Byte optional.Optional[byte] `xml:"byte"`
Float32 optional.Optional[float32] `xml:"float32"`
Expand Down Expand Up @@ -502,28 +563,28 @@ func Example_xmlMarshalEmpty() {

// Output:
// <s>
// <bool>false</bool>
// <byte>0</byte>
// <float32>0</float32>
// <float64>0</float64>
// <int16>0</int16>
// <int32>0</int32>
// <int64>0</int64>
// <int>0</int>
// <rune>0</rune>
// <bool></bool>
// <byte></byte>
// <float32></float32>
// <float64></float64>
// <int16></int16>
// <int32></int32>
// <int64></int64>
// <int></int>
// <rune></rune>
// <string></string>
// <time>0001-01-01T00:00:00Z</time>
// <uint16>0</uint16>
// <uint32>0</uint32>
// <uint64>0</uint64>
// <uint>0</uint>
// <uintptr>0</uintptr>
// <time></time>
// <uint16></uint16>
// <uint32></uint32>
// <uint64></uint64>
// <uint></uint>
// <uintptr></uintptr>
// </s>
}

func Example_xmlMarshalPresent() {
s := struct {
XMLName xml.Name `xml:"s"`
XMLName xml.Name `xml:"s"`
Bool optional.Optional[bool] `xml:"bool"`
Byte optional.Optional[byte] `xml:"byte"`
Float32 optional.Optional[float32] `xml:"float32"`
Expand Down Expand Up @@ -585,7 +646,7 @@ func Example_xmlMarshalPresent() {

func Example_xmlUnmarshalEmpty() {
s := struct {
XMLName xml.Name `xml:"s"`
XMLName xml.Name `xml:"s"`
Bool optional.Optional[bool] `xml:"bool"`
Byte optional.Optional[byte] `xml:"byte"`
Float32 optional.Optional[float32] `xml:"float32"`
Expand Down Expand Up @@ -646,7 +707,7 @@ func Example_xmlUnmarshalEmpty() {

func Example_xmlUnmarshalPresent() {
s := struct {
XMLName xml.Name `xml:"s"`
XMLName xml.Name `xml:"s"`
Bool optional.Optional[bool] `xml:"bool"`
Byte optional.Optional[byte] `xml:"byte"`
Float32 optional.Optional[float32] `xml:"float32"`
Expand Down
8 changes: 8 additions & 0 deletions go.mod
@@ -1,3 +1,11 @@
module 4d63.com/optional

go 1.18

require github.com/stretchr/testify v1.7.2

require (
github.com/davecgh/go-spew v1.1.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
20 changes: 17 additions & 3 deletions optional.go
Expand Up @@ -82,17 +82,28 @@ func (o Optional[T]) ElseZero() (value T) {
// representation of the zero value of the type wrapped if there is no value
// wrapped by this optional.
func (o Optional[T]) String() string {
return fmt.Sprintf("%v", o.ElseZero())
if v, ok := o.Get(); ok {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment for the function needs updating as it says if no value is set that the zero value of the type is returned, which is no longer the case.

return fmt.Sprintf("%v", v)
}
return ""
}

// MarshalJSON marshals the value being wrapped to JSON. If there is no vale
// being wrapped, the zero value of its type is marshaled.
func (o Optional[T]) MarshalJSON() (data []byte, err error) {
return json.Marshal(o.ElseZero())
if v, ok := o.Get(); ok {
return json.Marshal(v)
}
return []byte("null"), nil
}

// UnmarshalJSON unmarshals the JSON into a value wrapped by this optional.
func (o *Optional[T]) UnmarshalJSON(data []byte) error {
if string(data) == "null" {
*o = Empty[T]()
return nil
}

var v T
err := json.Unmarshal(data, &v)
if err != nil {
Expand All @@ -105,7 +116,10 @@ func (o *Optional[T]) UnmarshalJSON(data []byte) error {
// MarshalXML marshals the value being wrapped to XML. If there is no vale
// being wrapped, the zero value of its type is marshaled.
func (o Optional[T]) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
return e.EncodeElement(o.ElseZero(), start)
if v, ok := o.Get(); ok {
return e.EncodeElement(v, start)
}
return e.EncodeElement("", start)
}

// UnmarshalXML unmarshals the XML into a value wrapped by this optional.
Expand Down