diff --git a/.gitignore b/.gitignore index ee874ab..b9b263e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.DS_Store .golangci.yml # Binaries for programs and plugins *.exe diff --git a/sets.go b/sets.go index 014abb2..076bcf0 100644 --- a/sets.go +++ b/sets.go @@ -9,7 +9,6 @@ package sets // import "fortio.org/sets" import ( - "encoding/json" "fmt" "sort" "strings" @@ -190,37 +189,6 @@ func XOR[T comparable](a, b Set[T]) { RemoveCommon(a, b) } -// -- Serialization - -// MarshalJSON implements the json.Marshaler interface and only gets the elements as an array. -func (s Set[T]) MarshalJSON() ([]byte, error) { - // How to handle all ordered at once?? - switch v := any(s).(type) { - case Set[string]: - return json.Marshal(Sort(v)) - case Set[int]: - return json.Marshal(Sort(v)) - case Set[int8]: - return json.Marshal(Sort(v)) - case Set[int64]: - return json.Marshal(Sort(v)) - case Set[float64]: - return json.Marshal(Sort(v)) - default: - return json.Marshal(s.Elements()) - } -} - -// UnmarshalJSON implements the json.Unmarshaler interface turns the slice back to a Set. -func (s *Set[T]) UnmarshalJSON(data []byte) error { - var items []T - if err := json.Unmarshal(data, &items); err != nil { - return err - } - *s = New[T](items...) - return nil -} - // -- Additional operations on sets of ordered types // Sort returns a sorted slice of the elements in the set. diff --git a/sets_json.go b/sets_json.go new file mode 100644 index 0000000..5c8293e --- /dev/null +++ b/sets_json.go @@ -0,0 +1,44 @@ +// (Fortio) Sets. +// +// (c) 2023 Fortio Authors +// See LICENSE + +//go:build !no_json +// +build !no_json + +package sets // import "fortio.org/sets" + +import ( + "encoding/json" +) + +// -- Serialization + +// MarshalJSON implements the json.Marshaler interface and only gets the elements as an array. +func (s Set[T]) MarshalJSON() ([]byte, error) { + // How to handle all ordered at once?? + switch v := any(s).(type) { + case Set[string]: + return json.Marshal(Sort(v)) + case Set[int]: + return json.Marshal(Sort(v)) + case Set[int8]: + return json.Marshal(Sort(v)) + case Set[int64]: + return json.Marshal(Sort(v)) + case Set[float64]: + return json.Marshal(Sort(v)) + default: + return json.Marshal(s.Elements()) + } +} + +// UnmarshalJSON implements the json.Unmarshaler interface turns the slice back to a Set. +func (s *Set[T]) UnmarshalJSON(data []byte) error { + var items []T + if err := json.Unmarshal(data, &items); err != nil { + return err + } + *s = New[T](items...) + return nil +} diff --git a/sets_json_test.go b/sets_json_test.go new file mode 100644 index 0000000..12a01a2 --- /dev/null +++ b/sets_json_test.go @@ -0,0 +1,84 @@ +// Copyright (c) Fortio Authors, All Rights Reserved +// See LICENSE for licensing terms. (Apache-2.0) + +//go:build !no_json +// +build !no_json + +package sets_test + +import ( + "encoding/json" + "testing" + + "fortio.org/assert" + "fortio.org/sets" +) + +func TestJSON(t *testing.T) { + setA := sets.New("c,d", "a b", "y\000z", "mno") + b, err := json.Marshal(setA) + assert.NoError(t, err) + assert.Equal(t, `["a b","c,d","mno","y\u0000z"]`, string(b)) + jsonStr := `[ + "a,b", + "c,d" + ]` + setB := sets.New[string]() + err = json.Unmarshal([]byte(jsonStr), &setB) + assert.NoError(t, err) + assert.Equal(t, setB.Len(), 2) + assert.True(t, setB.Has("a,b")) + assert.True(t, setB.Has("c,d")) + setI := sets.New(3, 42, 7, 10) + b, err = json.Marshal(setI) + assert.NoError(t, err) + assert.Equal(t, `[3,7,10,42]`, string(b)) + smallIntSet := sets.New[int8](66, 65, 67) // if using byte, aka uint8, one gets base64("ABC") + b, err = json.Marshal(smallIntSet) + assert.NoError(t, err) + t.Logf("smallIntSet: %q", string(b)) + assert.Equal(t, `[65,66,67]`, string(b)) + floatSet := sets.New[float64](2.3, 1.1, -7.6, 42) + b, err = json.Marshal(floatSet) + assert.NoError(t, err) + t.Logf("floatSet: %q", string(b)) + assert.Equal(t, `[-7.6,1.1,2.3,42]`, string(b)) + i64Set := sets.New[int64](2, 1, -7, 42) + b, err = json.Marshal(i64Set) + assert.NoError(t, err) + t.Logf("i64Set: %q", string(b)) + assert.Equal(t, `[-7,1,2,42]`, string(b)) +} + +type foo struct { + X int +} + +func TestNonOrderedJSON(t *testing.T) { + s := sets.New( + foo{3}, + foo{1}, + foo{2}, + foo{4}, + ) + b, err := json.Marshal(s) + t.Logf("b: %s", string(b)) + assert.NoError(t, err) + // though I guess given it could be in any order it could be accidentally sorted too + assert.NotEqual(t, `[{"X":1},{"X":2},{"X":3},{"X":4}]`, string(b)) + u := sets.New[foo]() + err = json.Unmarshal(b, &u) + assert.NoError(t, err) + assert.Equal(t, 4, u.Len()) + assert.True(t, s.Equals(u)) +} + +func TestBadJson(t *testing.T) { + jsonStr := `[ + "a,b", + "c,d" + ]` + s := sets.New[int]() + err := json.Unmarshal([]byte(jsonStr), &s) + assert.Error(t, err) +} diff --git a/sets_test.go b/sets_test.go index 971cc89..b5e8876 100644 --- a/sets_test.go +++ b/sets_test.go @@ -4,7 +4,6 @@ package sets_test import ( - "encoding/json" "math" "math/rand" "testing" @@ -105,65 +104,6 @@ func TestSubset(t *testing.T) { assert.False(t, setB.Subset(setA)) } -func TestJSON(t *testing.T) { - setA := sets.New("c,d", "a b", "y\000z", "mno") - b, err := json.Marshal(setA) - assert.NoError(t, err) - assert.Equal(t, `["a b","c,d","mno","y\u0000z"]`, string(b)) - jsonStr := `[ - "a,b", - "c,d" - ]` - setB := sets.New[string]() - err = json.Unmarshal([]byte(jsonStr), &setB) - assert.NoError(t, err) - assert.Equal(t, setB.Len(), 2) - assert.True(t, setB.Has("a,b")) - assert.True(t, setB.Has("c,d")) - setI := sets.New(3, 42, 7, 10) - b, err = json.Marshal(setI) - assert.NoError(t, err) - assert.Equal(t, `[3,7,10,42]`, string(b)) - smallIntSet := sets.New[int8](66, 65, 67) // if using byte, aka uint8, one gets base64("ABC") - b, err = json.Marshal(smallIntSet) - assert.NoError(t, err) - t.Logf("smallIntSet: %q", string(b)) - assert.Equal(t, `[65,66,67]`, string(b)) - floatSet := sets.New[float64](2.3, 1.1, -7.6, 42) - b, err = json.Marshal(floatSet) - assert.NoError(t, err) - t.Logf("floatSet: %q", string(b)) - assert.Equal(t, `[-7.6,1.1,2.3,42]`, string(b)) - i64Set := sets.New[int64](2, 1, -7, 42) - b, err = json.Marshal(i64Set) - assert.NoError(t, err) - t.Logf("i64Set: %q", string(b)) - assert.Equal(t, `[-7,1,2,42]`, string(b)) -} - -type foo struct { - X int -} - -func TestNonOrderedJSON(t *testing.T) { - s := sets.New( - foo{3}, - foo{1}, - foo{2}, - foo{4}, - ) - b, err := json.Marshal(s) - t.Logf("b: %s", string(b)) - assert.NoError(t, err) - // though I guess given it could be in any order it could be accidentally sorted too - assert.NotEqual(t, `[{"X":1},{"X":2},{"X":3},{"X":4}]`, string(b)) - u := sets.New[foo]() - err = json.Unmarshal(b, &u) - assert.NoError(t, err) - assert.Equal(t, 4, u.Len()) - assert.True(t, s.Equals(u)) -} - func TestGenerate(t *testing.T) { setA := sets.New("a", "b", "c") res := sets.Tuplets(setA, 0) @@ -203,16 +143,6 @@ func TestNaNFloats(t *testing.T) { t.Fatal("Shouldn't be reached, should have paniced") } -func TestBadJson(t *testing.T) { - jsonStr := `[ - "a,b", - "c,d" - ]` - s := sets.New[int]() - err := json.Unmarshal([]byte(jsonStr), &s) - assert.Error(t, err) -} - func setup(b *testing.B, n int) sets.Set[int64] { s := sets.Set[int64]{} max := 8 * int64(n)