gojay

high performance JSON encoder/decoder with stream API for Golang
git clone git://git.lair.cx/gojay
Log | Files | Refs | README | LICENSE

commit c74d5aa06ea19e49f686cd8ed8505c7062a7971c
parent 5c45cc8f3d0ad91d4278919db98b40a4d57d8c4b
Author: Yongbin Kim <iam@yongbin.kim>
Date:   Mon, 10 Oct 2022 21:28:30 +0900

Added JSONObjectArray

MarshalerJSONObjectArray(arr []T) encodes object slice, UnmarshalerJSONObjectArray(arr *[]T, constructor func() T) decodes object slice.

Both uses generics, so, you can use `[]*someObject` directly.

Signed-off-by: Yongbin Kim <iam@yongbin.kim>

Diffstat:
Mdecode_slice.go | 22++++++++++++++++++++++
Mdecode_slice_test.go | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mencode_slice.go | 17+++++++++++++++++
Mencode_slice_test.go | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 157 insertions(+), 0 deletions(-)

diff --git a/decode_slice.go b/decode_slice.go @@ -1,5 +1,27 @@ package gojay +type unmarshalerJSONObjectArray[T UnmarshalerJSONObject] struct { + arr *[]T + constructor func() T +} + +// UnmarshalerJSONObjectArray returns UnmarshalerJSONArray interface for given slice +func UnmarshalerJSONObjectArray[T UnmarshalerJSONObject](arr *[]T, constructor func() T) UnmarshalerJSONArray { + return &unmarshalerJSONObjectArray[T]{ + arr: arr, + constructor: constructor, + } +} + +func (a *unmarshalerJSONObjectArray[T]) UnmarshalJSONArray(dec *Decoder) error { + t := a.constructor() + if err := dec.Object(t); err != nil { + return err + } + *a.arr = append(*a.arr, t) + return nil +} + // AddSliceString unmarshals the next JSON array of strings to the given *[]string s func (dec *Decoder) AddSliceString(s *[]string) error { return dec.SliceString(s) diff --git a/decode_slice_test.go b/decode_slice_test.go @@ -32,6 +32,64 @@ func (s *slicesTestObject) NKeys() int { return 4 } +func TestUnmarshalerJSONObjectArray(t *testing.T) { + testCases := []struct { + name string + json string + expectedResult []*slicesTestObject + err bool + }{ + { + name: "basic string array", + json: `[{ + "sliceString": ["foo","bar"] + }]`, + expectedResult: []*slicesTestObject{{ + sliceString: []string{"foo", "bar"}, + }}, + }, + { + name: "many string array", + json: `[{ + "sliceString": ["foo1","bar"] + },{ + "sliceString": ["foo2","bar"] + },{ + "sliceString": ["foo3","bar"] + }]`, + expectedResult: []*slicesTestObject{{ + sliceString: []string{"foo1", "bar"}, + }, { + sliceString: []string{"foo2", "bar"}, + }, { + sliceString: []string{"foo3", "bar"}, + }}, + }, + } + for _, testCase := range testCases { + t.Run( + testCase.name, + func(t *testing.T) { + dec := BorrowDecoder(strings.NewReader(testCase.json)) + var slice []*slicesTestObject + err := dec.Decode(UnmarshalerJSONObjectArray( + &slice, + func() *slicesTestObject { + return &slicesTestObject{} + }, + )) + + if testCase.err { + require.NotNil(t, err, "err should not be nil") + return + } + require.Nil(t, err, "err should be nil") + require.Equal(t, testCase.expectedResult, slice) + }, + ) + } +} + func TestDecodeSlices(t *testing.T) { testCases := []struct { name string diff --git a/encode_slice.go b/encode_slice.go @@ -1,5 +1,22 @@ package gojay +type marshalerObjectArray[T MarshalerJSONObject] []T + +// MarshalerJSONObjectArray returns MarshalerJSONArray interface for given slice +func MarshalerJSONObjectArray[T MarshalerJSONObject](arr []T) MarshalerJSONArray { + return marshalerObjectArray[T](arr) +} + +func (arr marshalerObjectArray[T]) MarshalJSONArray(enc *Encoder) { + for _, obj := range arr { + enc.Object(obj) + } +} + +func (arr marshalerObjectArray[T]) IsNil() bool { + return len(arr) == 0 +} + // AddSliceString marshals the given []string s func (enc *Encoder) AddSliceString(s []string) { enc.SliceString(s) diff --git a/encode_slice_test.go b/encode_slice_test.go @@ -18,6 +18,66 @@ func (s *slicesTestObject) IsNil() bool { return s == nil } +func TestMarshalerJSONObjectArray(t *testing.T) { + testCases := []struct { + name string + json string + obj []*slicesTestObject + }{ + { + name: "basic slice", + json: `[{ + "sliceString": ["foo","bar"], + "sliceInt": [], + "sliceFloat64": [], + "sliceBool": [] + }]`, + obj: []*slicesTestObject{{ + sliceString: []string{"foo", "bar"}, + }}, + }, + { + name: "many slices", + json: `[{ + "sliceString": ["foo1","bar"], + "sliceInt": [], + "sliceFloat64": [], + "sliceBool": [] + }, { + "sliceString": ["foo2","bar"], + "sliceInt": [], + "sliceFloat64": [], + "sliceBool": [] + }, { + "sliceString": ["foo3","bar"], + "sliceInt": [], + "sliceFloat64": [], + "sliceBool": [] + }]`, + obj: []*slicesTestObject{{ + sliceString: []string{"foo1", "bar"}, + }, { + sliceString: []string{"foo2", "bar"}, + }, { + sliceString: []string{"foo3", "bar"}, + }}, + }, + } + + for _, testCase := range testCases { + t.Run( + testCase.name, + func(t *testing.T) { + b := strings.Builder{} + enc := BorrowEncoder(&b) + err := enc.EncodeArray(MarshalerJSONObjectArray(testCase.obj)) + require.Nil(t, err, "err should be nil") + require.JSONEq(t, testCase.json, b.String()) + }, + ) + } +} + func TestEncodeSlices(t *testing.T) { testCases := []struct { name string