Skip to content

Commit a4184b5

Browse files
authored
feat: add JSON marshal/unmarshal support (#22)
2 parents 9743c82 + 767c48b commit a4184b5

17 files changed

Lines changed: 292 additions & 48 deletions

collection.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ package collection
44
type Collection[T any] interface {
55
Iterable[T]
66
Stringer
7+
JSONMarshaler
8+
JSONUnmarshaler
79

810
// Add adds the specified element to this collection.
911
Add(e T) bool

dict.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ type Dict[K comparable, V any] interface {
55
Iterable2[K, V]
66
DictIter[K, V]
77
Stringer
8+
JSONMarshaler
9+
JSONUnmarshaler
810

911
// Clear removes all key-value pairs in this dictionary.
1012
Clear()

dict/dict_go123_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import (
66
"github.com/ghosind/go-assert"
77
)
88

9-
func testDictIter(a *assert.Assertion, constructor dictTestConstructor) {
9+
func testDictIter(a *assert.Assertion, constructor dictConstructor) {
1010
d := constructor()
1111
for k, v := range testDataEn {
1212
d.Put(k, v)
@@ -23,7 +23,7 @@ func testDictIter(a *assert.Assertion, constructor dictTestConstructor) {
2323
}
2424
}
2525

26-
func testDictKeysIter(a *assert.Assertion, constructor dictTestConstructor) {
26+
func testDictKeysIter(a *assert.Assertion, constructor dictConstructor) {
2727
d := constructor()
2828
for k, v := range testDataEn {
2929
d.Put(k, v)
@@ -41,7 +41,7 @@ func testDictKeysIter(a *assert.Assertion, constructor dictTestConstructor) {
4141
}
4242
}
4343

44-
func testDictValuesIter(a *assert.Assertion, constructor dictTestConstructor) {
44+
func testDictValuesIter(a *assert.Assertion, constructor dictConstructor) {
4545
d := constructor()
4646
for k, v := range testDataEn {
4747
d.Put(k, v)

dict/dict_test.go

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package dict
22

33
import (
4+
"encoding/json"
45
"errors"
56
"fmt"
67
"sort"
@@ -10,7 +11,7 @@ import (
1011
"github.com/ghosind/go-assert"
1112
)
1213

13-
type dictTestConstructor func() collection.Dict[string, string]
14+
type dictConstructor func() collection.Dict[string, string]
1415

1516
var testDataEn = map[string]string{
1617
"zero": "0",
@@ -38,7 +39,7 @@ var testDataZh = map[string]string{
3839
"九": "9",
3940
}
4041

41-
func testDict(a *assert.Assertion, constructor dictTestConstructor) {
42+
func testDict(a *assert.Assertion, constructor dictConstructor) {
4243
testDictClear(a, constructor)
4344
testDictClone(a, constructor)
4445
testDictContainsKey(a, constructor)
@@ -57,9 +58,10 @@ func testDict(a *assert.Assertion, constructor dictTestConstructor) {
5758
testDictString(a, constructor)
5859
testDictValues(a, constructor)
5960
testDictValuesIter(a, constructor)
61+
testDictJSON(a, constructor)
6062
}
6163

62-
func testDictClear(a *assert.Assertion, constructor dictTestConstructor) {
64+
func testDictClear(a *assert.Assertion, constructor dictConstructor) {
6365
d := constructor()
6466
for k, v := range testDataEn {
6567
d.Put(k, v)
@@ -72,7 +74,7 @@ func testDictClear(a *assert.Assertion, constructor dictTestConstructor) {
7274
}
7375
}
7476

75-
func testDictClone(a *assert.Assertion, constructor dictTestConstructor) {
77+
func testDictClone(a *assert.Assertion, constructor dictConstructor) {
7678
d1 := constructor()
7779
for k, v := range testDataEn {
7880
d1.Put(k, v)
@@ -81,7 +83,7 @@ func testDictClone(a *assert.Assertion, constructor dictTestConstructor) {
8183
a.TrueNow(d1.Equals(d2))
8284
}
8385

84-
func testDictContainsKey(a *assert.Assertion, constructor dictTestConstructor) {
86+
func testDictContainsKey(a *assert.Assertion, constructor dictConstructor) {
8587
d := constructor()
8688
for k, v := range testDataEn {
8789
d.Put(k, v)
@@ -94,7 +96,7 @@ func testDictContainsKey(a *assert.Assertion, constructor dictTestConstructor) {
9496
}
9597
}
9698

97-
func testDictEquals(a *assert.Assertion, constructor dictTestConstructor) {
99+
func testDictEquals(a *assert.Assertion, constructor dictConstructor) {
98100
d1 := constructor()
99101
a.NotTrueNow(d1.Equals(nil))
100102

@@ -126,7 +128,7 @@ func testDictEquals(a *assert.Assertion, constructor dictTestConstructor) {
126128
a.NotTrueNow(d1.Equals(d3))
127129
}
128130

129-
func testDictForEach(a *assert.Assertion, constructor dictTestConstructor) {
131+
func testDictForEach(a *assert.Assertion, constructor dictConstructor) {
130132
d := constructor()
131133
for k, v := range testDataEn {
132134
d.Put(k, v)
@@ -152,7 +154,7 @@ func testDictForEach(a *assert.Assertion, constructor dictTestConstructor) {
152154
a.EqualNow(count, 1)
153155
}
154156

155-
func testDictGet(a *assert.Assertion, constructor dictTestConstructor) {
157+
func testDictGet(a *assert.Assertion, constructor dictConstructor) {
156158
d := constructor()
157159
for k, v := range testDataEn {
158160
d.Put(k, v)
@@ -170,7 +172,7 @@ func testDictGet(a *assert.Assertion, constructor dictTestConstructor) {
170172
}
171173
}
172174

173-
func testDictGetDefault(a *assert.Assertion, constructor dictTestConstructor) {
175+
func testDictGetDefault(a *assert.Assertion, constructor dictConstructor) {
174176
d := constructor()
175177
for k, v := range testDataEn {
176178
d.Put(k, v)
@@ -186,7 +188,7 @@ func testDictGetDefault(a *assert.Assertion, constructor dictTestConstructor) {
186188
}
187189
}
188190

189-
func testDictIsEmpty(a *assert.Assertion, constructor dictTestConstructor) {
191+
func testDictIsEmpty(a *assert.Assertion, constructor dictConstructor) {
190192
d := constructor()
191193
a.TrueNow(d.IsEmpty())
192194
for k, v := range testDataEn {
@@ -197,7 +199,7 @@ func testDictIsEmpty(a *assert.Assertion, constructor dictTestConstructor) {
197199
a.TrueNow(d.IsEmpty())
198200
}
199201

200-
func testDictKeys(a *assert.Assertion, constructor dictTestConstructor) {
202+
func testDictKeys(a *assert.Assertion, constructor dictConstructor) {
201203
d := constructor()
202204
for k, v := range testDataEn {
203205
d.Put(k, v)
@@ -209,7 +211,7 @@ func testDictKeys(a *assert.Assertion, constructor dictTestConstructor) {
209211
}
210212
}
211213

212-
func testDictPut(a *assert.Assertion, constructor dictTestConstructor) {
214+
func testDictPut(a *assert.Assertion, constructor dictConstructor) {
213215
d := constructor()
214216
for k, v := range testDataEn {
215217
d.Put(k, v)
@@ -218,7 +220,7 @@ func testDictPut(a *assert.Assertion, constructor dictTestConstructor) {
218220
a.EqualNow(d.Size(), len(testDataEn))
219221
}
220222

221-
func testDictRemove(a *assert.Assertion, constructor dictTestConstructor) {
223+
func testDictRemove(a *assert.Assertion, constructor dictConstructor) {
222224
d := constructor()
223225
for k, v := range testDataEn {
224226
d.Put(k, v)
@@ -230,7 +232,7 @@ func testDictRemove(a *assert.Assertion, constructor dictTestConstructor) {
230232
a.EqualNow(d.Size(), 0)
231233
}
232234

233-
func testDictReplace(a *assert.Assertion, constructor dictTestConstructor) {
235+
func testDictReplace(a *assert.Assertion, constructor dictConstructor) {
234236
d := constructor()
235237
for k, v := range testDataEn {
236238
d.Put(k, v)
@@ -249,7 +251,7 @@ func testDictReplace(a *assert.Assertion, constructor dictTestConstructor) {
249251
}
250252
}
251253

252-
func testDictSize(a *assert.Assertion, constructor dictTestConstructor) {
254+
func testDictSize(a *assert.Assertion, constructor dictConstructor) {
253255
d := constructor()
254256
a.EqualNow(d.Size(), 0)
255257
for k, v := range testDataEn {
@@ -260,7 +262,7 @@ func testDictSize(a *assert.Assertion, constructor dictTestConstructor) {
260262
a.EqualNow(d.Size(), 0)
261263
}
262264

263-
func testDictString(a *assert.Assertion, constructor dictTestConstructor) {
265+
func testDictString(a *assert.Assertion, constructor dictConstructor) {
264266
d := constructor()
265267
for k, v := range testDataEn {
266268
d.Put(k, v)
@@ -275,7 +277,7 @@ func testDictString(a *assert.Assertion, constructor dictTestConstructor) {
275277
}
276278
}
277279

278-
func testDictValues(a *assert.Assertion, constructor dictTestConstructor) {
280+
func testDictValues(a *assert.Assertion, constructor dictConstructor) {
279281
d := constructor()
280282
expectedValues := make([]string, 0, len(testDataEn))
281283
for k, v := range testDataEn {
@@ -291,3 +293,26 @@ func testDictValues(a *assert.Assertion, constructor dictTestConstructor) {
291293

292294
a.EqualNow(expectedValues, values)
293295
}
296+
297+
func testDictJSON(a *assert.Assertion, constructor dictConstructor) {
298+
d1 := constructor()
299+
for k, v := range testDataEn {
300+
d1.Put(k, v)
301+
}
302+
303+
b, err := d1.MarshalJSON()
304+
a.NilNow(err)
305+
306+
d2 := constructor()
307+
err = d2.UnmarshalJSON(b)
308+
a.NilNow(err)
309+
a.TrueNow(d1.Equals(d2))
310+
311+
d2.Clear()
312+
b, err = json.Marshal(d1)
313+
a.NilNow(err)
314+
315+
err = json.Unmarshal(b, d2)
316+
a.NilNow(err)
317+
a.TrueNow(d1.Equals(d2))
318+
}

dict/hash_dict.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package dict
22

33
import (
44
"bytes"
5+
"encoding/json"
56

67
"github.com/ghosind/collection"
78
"github.com/ghosind/collection/internal"
@@ -164,3 +165,18 @@ func (m *HashDict[K, V]) Values() []V {
164165

165166
return arr
166167
}
168+
169+
// MarshalJSON marshals the HashDict as a JSON object (map).
170+
func (m *HashDict[K, V]) MarshalJSON() ([]byte, error) {
171+
return json.Marshal(map[K]V(*m))
172+
}
173+
174+
// UnmarshalJSON unmarshals a JSON object into the HashDict.
175+
func (m *HashDict[K, V]) UnmarshalJSON(b []byte) error {
176+
var tmp map[K]V
177+
if err := json.Unmarshal(b, &tmp); err != nil {
178+
return err
179+
}
180+
*m = HashDict[K, V](tmp)
181+
return nil
182+
}

dict/sync_dict.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package dict
22

33
import (
44
"bytes"
5+
"encoding/json"
56
"sync"
67
"sync/atomic"
78

@@ -378,3 +379,45 @@ func (d *SyncDict[K, V]) Values() []V {
378379

379380
return keys
380381
}
382+
383+
// MarshalJSON marshals the SyncDict as a JSON object (map).
384+
func (d *SyncDict[K, V]) MarshalJSON() ([]byte, error) {
385+
read := d.loadPresentReadOnly()
386+
m := make(map[K]V)
387+
388+
for k, e := range read.M {
389+
v, ok := e.Load(d.zero)
390+
if !ok {
391+
continue
392+
}
393+
m[k] = v
394+
}
395+
396+
return json.Marshal(m)
397+
}
398+
399+
// UnmarshalJSON unmarshals a JSON object into the SyncDict.
400+
func (d *SyncDict[K, V]) UnmarshalJSON(b []byte) error {
401+
var tmp map[K]V
402+
if err := json.Unmarshal(b, &tmp); err != nil {
403+
return err
404+
}
405+
406+
d.mu.Lock()
407+
defer d.mu.Unlock()
408+
409+
if d.expunged == nil {
410+
// ensure expunged marker is initialized
411+
d.expunged = new(V)
412+
}
413+
414+
m := make(map[K]*internal.SyncEntry[V])
415+
for k, v := range tmp {
416+
m[k] = internal.NewSyncEntry(v, d.expunged)
417+
}
418+
d.read.Store(&internal.SyncReadOnly[K, V]{M: m})
419+
d.dirty = nil
420+
d.misses = 0
421+
422+
return nil
423+
}

encoding.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
package collection
22

3-
import "fmt"
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
)
47

58
// Stringer is an interface that wraps the basic String method.
69
type Stringer fmt.Stringer
10+
11+
// JSONMarshaler is an interface that wraps the basic MarshalJSON method.
12+
type JSONMarshaler json.Marshaler
13+
14+
// JSONUnmarshaler is an interface that wraps the basic UnmarshalJSON method.
15+
type JSONUnmarshaler json.Unmarshaler

list/array_list.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package list
22

33
import (
44
"bytes"
5+
"encoding/json"
56

67
"github.com/ghosind/collection"
78
"github.com/ghosind/collection/internal"
@@ -303,3 +304,18 @@ func (l *ArrayList[T]) ToSlice() []T {
303304

304305
return arr
305306
}
307+
308+
// MarshalJSON marshals the list as a JSON array.
309+
func (l *ArrayList[T]) MarshalJSON() ([]byte, error) {
310+
return json.Marshal(*l)
311+
}
312+
313+
// UnmarshalJSON unmarshals a JSON array into the list.
314+
func (l *ArrayList[T]) UnmarshalJSON(b []byte) error {
315+
var items []T
316+
if err := json.Unmarshal(b, &items); err != nil {
317+
return err
318+
}
319+
*l = ArrayList[T](items)
320+
return nil
321+
}

list/copy_on_write_array_list.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package list
22

33
import (
44
"bytes"
5+
"encoding/json"
56
"sync"
67

78
"github.com/ghosind/collection"
@@ -399,3 +400,21 @@ func (l *CopyOnWriteArrayList[T]) ToSlice() []T {
399400

400401
return slice
401402
}
403+
404+
// MarshalJSON marshals the copy-on-write list as a JSON array.
405+
func (l *CopyOnWriteArrayList[T]) MarshalJSON() ([]byte, error) {
406+
return json.Marshal(l.ToSlice())
407+
}
408+
409+
// UnmarshalJSON unmarshals a JSON array into the copy-on-write list.
410+
func (l *CopyOnWriteArrayList[T]) UnmarshalJSON(b []byte) error {
411+
var items []T
412+
if err := json.Unmarshal(b, &items); err != nil {
413+
return err
414+
}
415+
l.mu.Lock()
416+
defer l.mu.Unlock()
417+
l.data = make([]T, len(items))
418+
copy(l.data, items)
419+
return nil
420+
}

0 commit comments

Comments
 (0)