util/set: implement json.Marshaler/Unmarshaler (#10308)

Marshal as a JSON list instead of a map. Because set elements are
`comparable` and not `cmp.Ordered`, we cannot easily sort the items
before marshaling.

Updates #cleanup

Signed-off-by: Andrew Lytvynov <awly@tailscale.com>
This commit is contained in:
Andrew Lytvynov 2023-11-20 09:00:31 -07:00 committed by GitHub
parent dd8bc9ba03
commit 2c1f14d9e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 60 additions and 0 deletions

View File

@ -5,6 +5,7 @@
package set
import (
"encoding/json"
"maps"
)
@ -66,3 +67,16 @@ func (s Set[T]) Len() int { return len(s) }
func (s Set[T]) Equal(other Set[T]) bool {
return maps.Equal(s, other)
}
func (s Set[T]) MarshalJSON() ([]byte, error) {
return json.Marshal(s.Slice())
}
func (s *Set[T]) UnmarshalJSON(buf []byte) error {
var ss []T
if err := json.Unmarshal(buf, &ss); err != nil {
return err
}
*s = SetOf(ss)
return nil
}

View File

@ -4,6 +4,7 @@
package set
import (
"encoding/json"
"slices"
"testing"
)
@ -112,3 +113,48 @@ func TestClone(t *testing.T) {
t.Error("clone is not distinct from original")
}
}
func TestSetJSONRoundTrip(t *testing.T) {
tests := []struct {
desc string
strings Set[string]
ints Set[int]
}{
{"empty", make(Set[string]), make(Set[int])},
{"nil", nil, nil},
{"one-item", SetOf([]string{"one"}), SetOf([]int{1})},
{"multiple-items", SetOf([]string{"one", "two", "three"}), SetOf([]int{1, 2, 3})},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
t.Run("strings", func(t *testing.T) {
buf, err := json.Marshal(tt.strings)
if err != nil {
t.Fatalf("json.Marshal: %v", err)
}
t.Logf("marshaled: %s", buf)
var s Set[string]
if err := json.Unmarshal(buf, &s); err != nil {
t.Fatalf("json.Unmarshal: %v", err)
}
if !s.Equal(tt.strings) {
t.Errorf("set changed after JSON marshal/unmarshal, before: %v, after: %v", tt.strings, s)
}
})
t.Run("ints", func(t *testing.T) {
buf, err := json.Marshal(tt.ints)
if err != nil {
t.Fatalf("json.Marshal: %v", err)
}
t.Logf("marshaled: %s", buf)
var s Set[int]
if err := json.Unmarshal(buf, &s); err != nil {
t.Fatalf("json.Unmarshal: %v", err)
}
if !s.Equal(tt.ints) {
t.Errorf("set changed after JSON marshal/unmarshal, before: %v, after: %v", tt.ints, s)
}
})
})
}
}