Skip to content

Commit

Permalink
sq
Browse files Browse the repository at this point in the history
  • Loading branch information
jamietanna committed Jan 3, 2024
1 parent 2fafecc commit 4db874b
Show file tree
Hide file tree
Showing 2 changed files with 203 additions and 31 deletions.
48 changes: 33 additions & 15 deletions types/nullable.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,49 @@ import (
// nullBytes is a JSON null literal
var nullBytes = []byte("null")

// Nullable type which can help distinguish between if a value was explicitly
// Nullable allows defining that a
// provided `null` in JSON or not
type Nullable[T any] struct {
Value T
Set bool
Null bool
// Value contains the underlying value of the field. If `Set` is true, and `Null` is false, **??**
Value *T
// Set will be true if the field was sent.
Set bool
}

// UnmarshalJSON implements the Unmarshaler interface.
func (t *Nullable[T]) UnmarshalJSON(data []byte) error {
t.Set = true
if bytes.Equal(data, nullBytes) {
t.Null = true
// t.Null = true
return nil
}
if err := json.Unmarshal(data, &t.Value); err != nil {
// fmt.Printf("data: %v\n", data)
// fmt.Printf("t.Value: %v\n", t.Value)
var tt T
if err := json.Unmarshal(data, &tt); err != nil {
return fmt.Errorf("couldn't unmarshal JSON: %w", err)
}
t.Null = false
// fmt.Printf("t.Value: %v\n", t.Value)
t.Value = &tt
// fmt.Printf("t.Value: %v\n", t.Value)
// fmt.Printf("t.Value: %v\n", *t.Value)
// t.Null = false
return nil
}

// MarshalJSON implements the Marshaler interface.
func (t Nullable[T]) MarshalJSON() ([]byte, error) {
// TODO
// TODO
// TODO
// if !t.Set {
// // return []byte(""), nil
// return nil, nil
// }
// TODO
// TODO
// TODO

if t.IsNull() {
return nullBytes, nil
}
Expand All @@ -41,14 +60,13 @@ func (t Nullable[T]) MarshalJSON() ([]byte, error) {

// IsNull returns true if the value is explicitly provided `null` in json
func (t *Nullable[T]) IsNull() bool {
return t.Null
}

// IsSet returns true if the value is provided in json
func (t *Nullable[T]) IsSet() bool {
return t.Set
return t.Value == nil
}

func (t *Nullable[T]) Get() (value T, null bool) {
return t.Value, t.IsNull()
// Get retrieves the value of underlying nullable field, and indicates whether the value was set or not.
// If `set == false`, then `value` can be ignored
// If `set == true` and `value == nil`: the field was sent explicitly with the value `null`
// If `set == true` and `value != nil`: the field was sent with the contents at `*value`
func (t *Nullable[T]) Get() (value *T, set bool) {
return t.Value, t.Set
}
186 changes: 170 additions & 16 deletions types/nullable_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,158 @@ package types
import (
"encoding/json"
"fmt"
"github.com/stretchr/testify/assert"
"testing"

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

func ExampleNullable_marshal() {
obj := struct {
ID Nullable[int] `json:"id"`
}{}

// when it's not set
b, err := json.Marshal(obj)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf(`JSON: %s`+"\n", b)
fmt.Println("---")

// when it's set explicitly to nil
obj.ID.Value = nil
obj.ID.Set = true

b, err = json.Marshal(obj)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf(`JSON: %s`+"\n", b)
fmt.Println("---")

// when it's set explicitly to the zero value
var v int
obj.ID.Value = &v
obj.ID.Set = true

b, err = json.Marshal(obj)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf(`JSON: %s`+"\n", b)
fmt.Println("---")

// when it's set explicitly to a specific value
v = 12345
obj.ID.Value = &v
obj.ID.Set = true

b, err = json.Marshal(obj)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf(`JSON: %s`+"\n", b)
fmt.Println("---")

// Output:
// JSON: {}
// ---
// JSON: {"id":null}
// ---
// JSON: {"id":0}
// ---
// JSON: {"id":12345}
// ---
}

func ExampleNullable_unmarshal() {
obj := struct {
Name Nullable[string] `json:"name"`
}{}

// when it's not set
err := json.Unmarshal([]byte(`
{
}
`), &obj)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("obj.Name.Set: %v\n", obj.Name.Set)
fmt.Printf("obj.Name.Value: %v\n", obj.Name.Value)
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.Printf("obj.Name.Set: %v\n", obj.Name.Set)
fmt.Printf("obj.Name.Value: %v\n", obj.Name.Value)
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.Printf("obj.Name.Set: %v\n", obj.Name.Set)
if obj.Name.Value == nil {
fmt.Println("Error: expected obj.Name.Value to have a value, but was <nil>")
return
}
fmt.Printf("obj.Name.Value: %#v\n", *obj.Name.Value)
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.Printf("obj.Name.Set: %v\n", obj.Name.Set)
if obj.Name.Value == nil {
fmt.Println("Error: expected obj.Name.Value to have a value, but was <nil>")
return
}
fmt.Printf("obj.Name.Value: %#v\n", *obj.Name.Value)
fmt.Println("---")

// Output:
// obj.Name.Set: false
// obj.Name.Value: <nil>
// ---
// obj.Name.Set: true
// obj.Name.Value: <nil>
// ---
// obj.Name.Set: true
// obj.Name.Value: ""
// ---
// obj.Name.Set: true
// obj.Name.Value: "foo"
// ---
}

type SimpleString struct {
Name Nullable[string] `json:"name"`
}
Expand Down Expand Up @@ -53,7 +201,7 @@ func TestSimpleString(t *testing.T) {
err := json.Unmarshal(tt.jsonInput, &obj)
assert.NoError(t, err)
assert.Equalf(t, tt.wantNull, obj.Name.IsNull(), "IsNull()")
assert.Equalf(t, tt.wantSet, obj.Name.IsSet(), "IsSet()")
assert.Equalf(t, tt.wantSet, obj.Name.Set, "Set")
fmt.Println(obj.Name.Get())
})
}
Expand Down Expand Up @@ -105,7 +253,7 @@ func TestSimpleInt(t *testing.T) {
err := json.Unmarshal(tt.jsonInput, &obj)
assert.NoError(t, err)
assert.Equalf(t, tt.wantNull, obj.ReplicaCount.IsNull(), "IsNull()")
assert.Equalf(t, tt.wantSet, obj.ReplicaCount.IsSet(), "IsSet()")
assert.Equalf(t, tt.wantSet, obj.ReplicaCount.Set, "Set")
})
}
}
Expand Down Expand Up @@ -157,7 +305,7 @@ func TestSimplePointerInt(t *testing.T) {
err := json.Unmarshal(tt.jsonInput, &obj)
assert.NoError(t, err)
assert.Equalf(t, tt.wantNull, obj.ReplicaCount.IsNull(), "IsNull()")
assert.Equalf(t, tt.wantSet, obj.ReplicaCount.IsSet(), "IsSet()")
assert.Equalf(t, tt.wantSet, obj.ReplicaCount.Set, "Set")
})
}
}
Expand All @@ -179,11 +327,14 @@ func TestMixed(t *testing.T) {
name: "empty json input",
jsonInput: []byte(`{}`),
assert: func(obj TestComplex, t *testing.T) {
assert.Equalf(t, false, obj.SimpleInt.Value.ReplicaCount.IsSet(), "replica count should not be set")
require.NotNilf(t, obj.SimpleInt.Value, "NN")
require.NotNilf(t, obj.SimpleString.Value, "NN")
require.NotNilf(t, obj.StringList.Value, "NN")
assert.Equalf(t, false, obj.SimpleInt.Value.ReplicaCount.Set, "replica count should not be set")
assert.Equalf(t, false, obj.SimpleInt.Value.ReplicaCount.IsNull(), "replica count should not be null")
assert.Equalf(t, false, obj.SimpleString.Value.Name.IsSet(), "name should not be set")
assert.Equalf(t, false, obj.SimpleString.Value.Name.Set, "name should not be set")
assert.Equalf(t, false, obj.SimpleString.Value.Name.IsNull(), "name should not be null")
assert.Equalf(t, false, obj.StringList.IsSet(), "string list should not be set")
assert.Equalf(t, false, obj.StringList.Set, "string list should not be set")
assert.Equalf(t, false, obj.StringList.IsNull(), "string list should not be null")
},
},
Expand All @@ -192,21 +343,24 @@ func TestMixed(t *testing.T) {
name: "replica count having non null value",
jsonInput: []byte(`{"simple_int":{"replicaCount":1}}`),
assert: func(obj TestComplex, t *testing.T) {
require.NotNilf(t, obj.SimpleInt.Value, "NN")
require.NotNilf(t, obj.SimpleString.Value, "NN")
assert.Equalf(t, false, obj.SimpleInt.Value.ReplicaCount.IsNull(), "replica count should NOT be null")
assert.Equalf(t, true, obj.SimpleInt.Value.ReplicaCount.IsSet(), "replica count should be set")
assert.Equalf(t, false, obj.SimpleString.Value.Name.IsSet(), "name should NOT be set")
assert.Equalf(t, true, obj.SimpleInt.Value.ReplicaCount.Set, "replica count should be set")
assert.Equalf(t, false, obj.SimpleString.Value.Name.Set, "name should NOT be set")
assert.Equalf(t, false, obj.SimpleString.Value.Name.IsNull(), "name should NOT be null")
gotValue, isNull := obj.SimpleInt.Value.ReplicaCount.Get()
assert.Equalf(t, false, isNull, "replica count should NOT be null")
assert.Equalf(t, 1, gotValue, "replica count should be 1")
gotValue, isSet := obj.SimpleInt.Value.ReplicaCount.Get()
assert.Equalf(t, true, isSet, "replica count should NOT be null")
assert.Equalf(t, 1, *gotValue, "replica count should be 1")
},
},

{
name: "string list having null value",
jsonInput: []byte(`{"string_list": null}`),
assert: func(obj TestComplex, t *testing.T) {
assert.Equalf(t, true, obj.StringList.IsSet(), "string_list should be set")
require.NotNilf(t, obj.StringList.Value, "NN")
assert.Equalf(t, true, obj.StringList.Set, "string_list should be set")
assert.Equalf(t, true, obj.StringList.IsNull(), "string_list should be null")
},
},
Expand All @@ -215,7 +369,7 @@ func TestMixed(t *testing.T) {
name: "string list having non null value",
jsonInput: []byte(`{"string_list": ["foo", "bar"]}`),
assert: func(obj TestComplex, t *testing.T) {
assert.Equalf(t, true, obj.StringList.IsSet(), "string_list should be set")
assert.Equalf(t, true, obj.StringList.Set, "string_list should be set")
assert.Equalf(t, false, obj.StringList.IsNull(), "string_list should not be null")
gotStringList, isNull := obj.StringList.Get()
assert.Equalf(t, false, isNull, "string_list should not be null")
Expand All @@ -228,7 +382,7 @@ func TestMixed(t *testing.T) {
name: "set string list having empty value",
jsonInput: []byte(`{"string_list":[]}`),
assert: func(obj TestComplex, t *testing.T) {
assert.Equalf(t, true, obj.StringList.IsSet(), "string_list should be set")
assert.Equalf(t, true, obj.StringList.Set, "string_list should be set")
assert.Equalf(t, false, obj.StringList.IsNull(), "string_list should not be null")
gotStringList, isNull := obj.StringList.Get()
assert.Equalf(t, false, isNull, "string_list should not be null")
Expand All @@ -238,7 +392,7 @@ func TestMixed(t *testing.T) {
},
}
for _, tt := range tests {
t.Run(tt.name, func(t1 *testing.T) {
t.Run(tt.name, func(t *testing.T) {
var obj TestComplex
err := json.Unmarshal(tt.jsonInput, &obj)
assert.NoError(t, err)
Expand Down

0 comments on commit 4db874b

Please sign in to comment.