Skip to content

Commit

Permalink
Omittable can now be serialized as json (#2839)
Browse files Browse the repository at this point in the history
  • Loading branch information
endSly committed Nov 7, 2023
1 parent dcb7619 commit c0ca509
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 0 deletions.
23 changes: 23 additions & 0 deletions graphql/omittable.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
package graphql

import "encoding/json"

// Omittable is a wrapper around a value that also stores whether it is set
// or not.
type Omittable[T any] struct {
value T
set bool
}

var (
_ json.Marshaler = Omittable[struct{}]{}
_ json.Unmarshaler = (*Omittable[struct{}])(nil)
)

func OmittableOf[T any](value T) Omittable[T] {
return Omittable[T]{
value: value,
Expand All @@ -33,3 +40,19 @@ func (o Omittable[T]) ValueOK() (T, bool) {
func (o Omittable[T]) IsSet() bool {
return o.set
}

func (o Omittable[T]) MarshalJSON() ([]byte, error) {
if !o.set {
return []byte("null"), nil
}
return json.Marshal(o.value)
}

func (o *Omittable[T]) UnmarshalJSON(bytes []byte) error {
err := json.Unmarshal(bytes, &o.value)
if err != nil {
return err
}
o.set = true
return nil
}
88 changes: 88 additions & 0 deletions graphql/omittable_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package graphql

import (
"encoding/json"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestOmittable_MarshalJSON(t *testing.T) {
s := "test"
testCases := []struct {
name string
input any
expectedJSON string
}{
{
name: "simple string",
input: struct{ Value Omittable[string] }{Value: OmittableOf("simple string")},
expectedJSON: `{"Value": "simple string"}`,
},
{
name: "string pointer",
input: struct{ Value Omittable[*string] }{Value: OmittableOf(&s)},
expectedJSON: `{"Value": "test"}`,
},
{
name: "omitted integer",
input: struct{ Value Omittable[int] }{},
expectedJSON: `{"Value": null}`,
},
{
name: "omittable omittable",
input: struct{ Value Omittable[Omittable[uint64]] }{Value: OmittableOf(OmittableOf(uint64(42)))},
expectedJSON: `{"Value": 42}`,
},
{
name: "omittable struct",
input: struct {
Value Omittable[struct{ Inner string }]
}{Value: OmittableOf(struct{ Inner string }{})},
expectedJSON: `{"Value": {"Inner": ""}}`,
},
{
name: "omitted struct",
input: struct {
Value Omittable[struct{ Inner string }]
}{},
expectedJSON: `{"Value": null}`,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
data, err := json.Marshal(tc.input)
require.NoError(t, err)
assert.JSONEq(t, tc.expectedJSON, string(data))
})
}
}

func TestOmittable_UnmarshalJSON(t *testing.T) {
var s struct {
String Omittable[string]
OmittedString Omittable[string]
StringPointer Omittable[*string]
NullInt Omittable[int]
}

err := json.Unmarshal([]byte(`
{
"String": "simple string",
"StringPointer": "string pointer",
"NullInt": null
}`), &s)

require.NoError(t, err)
assert.Equal(t, "simple string", s.String.Value())
assert.True(t, s.String.IsSet())
assert.False(t, s.OmittedString.IsSet())
assert.True(t, s.StringPointer.IsSet())
if assert.NotNil(t, s.StringPointer.Value()) {
assert.EqualValues(t, "string pointer", *s.StringPointer.Value())
}
assert.True(t, s.NullInt.IsSet())
assert.Zero(t, s.NullInt.Value())
}

0 comments on commit c0ca509

Please sign in to comment.