gojay

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

commit a7c12f17ccf0509450fc93480dfc47773aa6e026
parent bf5c9d91c10918ce5ef0762a113ca076611f7281
Author: Francois Parquet <francois.parquet@gmail.com>
Date:   Sun, 24 Mar 2019 02:05:14 +0800

Merge pull request #104 from francoispqt/feature/built-in-slices

Feature/built in slices
Diffstat:
Mdecode_array.go | 2+-
Mdecode_array_test.go | 20++++++++++++++++++++
Mdecode_number_int_test.go | 24++++++++++++++++++++++++
Mdecode_object_test.go | 172++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Adecode_slice.go | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adecode_slice_test.go | 125+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdecode_sqlnull_test.go | 51+++++++++++++++++++++++++++++++++++++++++++++++----
Mdecode_string_test.go | 8++++++++
Aencode_slice.go | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aencode_slice_test.go | 187+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
10 files changed, 785 insertions(+), 6 deletions(-)

diff --git a/decode_array.go b/decode_array.go @@ -212,7 +212,7 @@ func (dec *Decoder) AddArray(v UnmarshalerJSONArray) error { } // AddArrayNull decodes the JSON value within an object or an array to a UnmarshalerJSONArray. -func (dec *Decoder) AddArrayNull(v UnmarshalerJSONArray) error { +func (dec *Decoder) AddArrayNull(v interface{}) error { return dec.ArrayNull(v) } diff --git a/decode_array_test.go b/decode_array_test.go @@ -412,6 +412,26 @@ func TestDecodeArrayNullPtr(t *testing.T) { assert.Nil(t, err) assert.Nil(t, o.SubArray) }) + t.Run("sub array err, not closing arr", func(t *testing.T) { + var o = &ObjectArrayNull{} + var err = UnmarshalJSONObject([]byte(`{"subarray": [ `), o) + assert.NotNil(t, err) + }) + t.Run("sub array err, invalid string", func(t *testing.T) { + var o = &ObjectArrayNull{} + var err = UnmarshalJSONObject([]byte(`{"subarray":[",]}`), o) + assert.NotNil(t, err) + }) + t.Run("sub array err, invalid null", func(t *testing.T) { + var o = &ObjectArrayNull{} + var err = UnmarshalJSONObject([]byte(`{"subarray":nll}`), o) + assert.NotNil(t, err) + }) + t.Run("sub array err, empty", func(t *testing.T) { + var o = &ObjectArrayNull{} + var err = UnmarshalJSONObject([]byte(`{"subarray":`), o) + assert.NotNil(t, err) + }) } type testChannelArray chan *TestObj diff --git a/decode_number_int_test.go b/decode_number_int_test.go @@ -735,6 +735,12 @@ func TestDecoderInt64(t *testing.T) { expectedResult: 0, }, { + name: "before-exp-err-too-big", + json: "10.11231242345325435464364643e1", + expectedResult: 0, + err: true, + }, + { name: "error3", json: "0E40", expectedResult: 0, @@ -1402,6 +1408,12 @@ func TestDecoderInt32(t *testing.T) { expectedResult: -800000, }, { + name: "before-exp-err-too-big", + json: "10.11231242345325435464364643e1", + expectedResult: 0, + err: true, + }, + { name: "exponent-err-", json: "0.1e", expectedResult: 0, @@ -2017,6 +2029,11 @@ func TestDecoderInt16(t *testing.T) { expectedResult: 120, }, { + name: "exponent too big", + json: "1000.202302302422324435342E2", + err: true, + }, + { name: "basic-exponent-positive-positive-exp1", json: "3.5e+001 ", expectedResult: 35, @@ -2751,6 +2768,7 @@ func TestDecoderInt8(t *testing.T) { json: "-3e01", expectedResult: -30, }, + { name: "error3", json: "0E40", @@ -2765,6 +2783,12 @@ func TestDecoderInt8(t *testing.T) { err: true, }, { + name: "before-exp-err-too-big", + json: "10.11231242345325435464364643e1", + expectedResult: 0, + err: true, + }, + { name: "exponent-err-too-big", json: "0.1e10000000000000000000", expectedResult: 0, diff --git a/decode_object_test.go b/decode_object_test.go @@ -939,7 +939,7 @@ func (o *ObjectNull) UnmarshalJSONObject(dec *Decoder, k string) error { case "subobject": return dec.ObjectNull(&o.SubObject) case "subarray": - return dec.ArrayNull(&o.SubArray) + return dec.AddArrayNull(&o.SubArray) } return nil } @@ -948,6 +948,25 @@ func (o *ObjectNull) NKeys() int { return 2 } +type ObjectNullZeroNKeys struct { + SubObject *ObjectNullZeroNKeys + SubArray *testSliceBools +} + +func (o *ObjectNullZeroNKeys) UnmarshalJSONObject(dec *Decoder, k string) error { + switch k { + case "subobject": + return dec.AddObjectNull(&o.SubObject) + case "subarray": + return dec.AddArrayNull(&o.SubArray) + } + return nil +} + +func (o *ObjectNullZeroNKeys) NKeys() int { + return 0 +} + func TestDecodeObjectNull(t *testing.T) { t.Run("sub obj should not be nil", func(t *testing.T) { var o = &ObjectNull{} @@ -986,6 +1005,49 @@ func TestDecodeObjectNull(t *testing.T) { }, ) t.Run( + "skip data", + func(t *testing.T) { + var o = &ObjectNull{} + var err = UnmarshalJSONObject([]byte(`{ + "subobject": { + "subobject": {}, + "subarray": [], + "subarray": [], + "skipped": "" + } + }`), DecodeObjectFunc(func(dec *Decoder, k string) error { + return dec.ObjectNull(&o.SubObject) + })) + assert.Nil(t, err) + assert.NotNil(t, o.SubObject) + assert.Nil(t, o.SubArray) + }, + ) + t.Run( + "skip data not child", + func(t *testing.T) { + var o = &ObjectNull{} + var dec = NewDecoder(strings.NewReader(`{ + "subobject": {}, + "subarray": [], + "subarray": [], + "skipped": "" + }`)) + var _, err = dec.decodeObjectNull(&o) + assert.Nil(t, err) + assert.NotNil(t, o.SubObject) + }, + ) + t.Run( + "err empty json", + func(t *testing.T) { + var o = &ObjectNull{} + var dec = NewDecoder(strings.NewReader(``)) + var _, err = dec.decodeObjectNull(&o) + assert.NotNil(t, err) + }, + ) + t.Run( "should return an error as type is not ptr", func(t *testing.T) { var err = UnmarshalJSONObject([]byte(`{"key":{}}`), DecodeObjectFunc(func(dec *Decoder, k string) error { @@ -1064,6 +1126,17 @@ func TestDecodeObjectNull(t *testing.T) { "invalid JSON for object", func(t *testing.T) { var o = &ObjectNull{} + var err = UnmarshalJSONObject([]byte(`{"subobject":{"subobject":{"a":a}`), DecodeObjectFunc(func(dec *Decoder, k string) error { + return dec.ObjectNull(&o.SubObject) + })) + assert.NotNil(t, err) + assert.IsType(t, InvalidJSONError(""), err) + }, + ) + t.Run( + "invalid JSON for object", + func(t *testing.T) { + var o = &ObjectNull{} var err = UnmarshalJSONObject([]byte(`{"subobject":{"subobject":a}`), DecodeObjectFunc(func(dec *Decoder, k string) error { return dec.ObjectNull(&o.SubObject) })) @@ -1074,6 +1147,17 @@ func TestDecodeObjectNull(t *testing.T) { t.Run( "invalid JSON for object", func(t *testing.T) { + var o = &ObjectNull{} + var err = UnmarshalJSONObject([]byte(`{"subobject":{"subobject":{"sub}}`), DecodeObjectFunc(func(dec *Decoder, k string) error { + return dec.ObjectNull(&o.SubObject) + })) + assert.NotNil(t, err) + assert.IsType(t, InvalidJSONError(""), err) + }, + ) + t.Run( + "invalid JSON for object", + func(t *testing.T) { var o = &testSliceBools{} var err = UnmarshalJSONObject([]byte(`{"subobject":a`), DecodeObjectFunc(func(dec *Decoder, k string) error { return dec.ArrayNull(&o) @@ -1122,6 +1206,92 @@ func TestDecodeObjectNull(t *testing.T) { assert.IsType(t, InvalidJSONError(""), err) }, ) + t.Run( + "zero nkeys, no error, two keys", + func(t *testing.T) { + var o = &ObjectNullZeroNKeys{} + var err = UnmarshalJSONObject([]byte(`{ + "subobject": { + "subobject": { + "subobject":{} + }, + "subarray": [] + } + }`), DecodeObjectFunc(func(dec *Decoder, k string) error { + return dec.ObjectNull(&o.SubObject) + })) + assert.Nil(t, err) + }, + ) + t.Run( + "zero nkeys, no error, two keys, skip data", + func(t *testing.T) { + var o = &ObjectNullZeroNKeys{} + var err = UnmarshalJSONObject([]byte(`{ + "subobject": { + "subobject": { + "subobject":{} + }, + "subarray": [], + "skipped": 1 + } + }`), DecodeObjectFunc(func(dec *Decoder, k string) error { + return dec.ObjectNull(&o.SubObject) + })) + assert.Nil(t, err) + }, + ) + t.Run( + "zero nkeys, error skip data", + func(t *testing.T) { + var o = &ObjectNullZeroNKeys{} + var err = UnmarshalJSONObject([]byte(`{ + "subobject": { + "subobject": { + "subobject":{} + }, + "subarray": [], + "skippedInvalid": "q + } + }`), DecodeObjectFunc(func(dec *Decoder, k string) error { + return dec.ObjectNull(&o.SubObject) + })) + assert.NotNil(t, err) + assert.IsType(t, InvalidJSONError(""), err) + }, + ) + t.Run( + "zero nkeys, error invalid json in keys", + func(t *testing.T) { + var o = &ObjectNullZeroNKeys{} + var err = UnmarshalJSONObject([]byte(`{ + "subobject": { + "subobj + } + }`), DecodeObjectFunc(func(dec *Decoder, k string) error { + return dec.ObjectNull(&o.SubObject) + })) + assert.NotNil(t, err) + assert.IsType(t, InvalidJSONError(""), err) + }, + ) + t.Run( + "zero nkeys, error invalid json, sub object", + func(t *testing.T) { + var o = &ObjectNullZeroNKeys{} + var err = UnmarshalJSONObject([]byte(`{ + "subobject": { + "subobject": { + "subobj + } + } + }`), DecodeObjectFunc(func(dec *Decoder, k string) error { + return dec.ObjectNull(&o.SubObject) + })) + assert.NotNil(t, err) + assert.IsType(t, InvalidJSONError(""), err) + }, + ) } func TestDecodeObjectComplex(t *testing.T) { diff --git a/decode_slice.go b/decode_slice.go @@ -0,0 +1,89 @@ +package gojay + +// AddSliceString unmarshals the next JSON array of strings to the given *[]string s +func (dec *Decoder) AddSliceString(s *[]string) error { + return dec.SliceString(s) +} + +// SliceString unmarshals the next JSON array of strings to the given *[]string s +func (dec *Decoder) SliceString(s *[]string) error { + err := dec.Array(DecodeArrayFunc(func(dec *Decoder) error { + var str string + if err := dec.String(&str); err != nil { + return err + } + *s = append(*s, str) + return nil + })) + + if err != nil { + return err + } + return nil +} + +// AddSliceInt unmarshals the next JSON array of integers to the given *[]int s +func (dec *Decoder) AddSliceInt(s *[]int) error { + return dec.SliceInt(s) +} + +// SliceInt unmarshals the next JSON array of integers to the given *[]int s +func (dec *Decoder) SliceInt(s *[]int) error { + err := dec.Array(DecodeArrayFunc(func(dec *Decoder) error { + var i int + if err := dec.Int(&i); err != nil { + return err + } + *s = append(*s, i) + return nil + })) + + if err != nil { + return err + } + return nil +} + +// AddFloat64 unmarshals the next JSON array of floats to the given *[]float64 s +func (dec *Decoder) AddSliceFloat64(s *[]float64) error { + return dec.SliceFloat64(s) +} + +// SliceFloat64 unmarshals the next JSON array of floats to the given *[]float64 s +func (dec *Decoder) SliceFloat64(s *[]float64) error { + err := dec.Array(DecodeArrayFunc(func(dec *Decoder) error { + var i float64 + if err := dec.Float64(&i); err != nil { + return err + } + *s = append(*s, i) + return nil + })) + + if err != nil { + return err + } + return nil +} + +// AddBool unmarshals the next JSON array of boolegers to the given *[]bool s +func (dec *Decoder) AddSliceBool(s *[]bool) error { + return dec.SliceBool(s) +} + +// SliceBool unmarshals the next JSON array of boolegers to the given *[]bool s +func (dec *Decoder) SliceBool(s *[]bool) error { + err := dec.Array(DecodeArrayFunc(func(dec *Decoder) error { + var b bool + if err := dec.Bool(&b); err != nil { + return err + } + *s = append(*s, b) + return nil + })) + + if err != nil { + return err + } + return nil +} diff --git a/decode_slice_test.go b/decode_slice_test.go @@ -0,0 +1,125 @@ +package gojay + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +type slicesTestObject struct { + sliceString []string + sliceInt []int + sliceFloat64 []float64 + sliceBool []bool +} + +func (s *slicesTestObject) UnmarshalJSONObject(dec *Decoder, k string) error { + switch k { + case "sliceString": + return dec.AddSliceString(&s.sliceString) + case "sliceInt": + return dec.AddSliceInt(&s.sliceInt) + case "sliceFloat64": + return dec.AddSliceFloat64(&s.sliceFloat64) + case "sliceBool": + return dec.AddSliceBool(&s.sliceBool) + } + return nil +} + +func (s *slicesTestObject) NKeys() int { + return 4 +} + +func TestDecodeSlices(t *testing.T) { + testCases := []struct { + name string + json string + expectedResult slicesTestObject + err bool + }{ + { + name: "basic slice string", + json: `{ + "sliceString": ["foo","bar"] + }`, + expectedResult: slicesTestObject{ + sliceString: []string{"foo", "bar"}, + }, + }, + { + name: "basic slice bool", + json: `{ + "sliceBool": [true,false] + }`, + expectedResult: slicesTestObject{ + sliceBool: []bool{true, false}, + }, + }, + { + name: "basic slice int", + json: `{ + "sliceInt": [1,2,3] + }`, + expectedResult: slicesTestObject{ + sliceInt: []int{1, 2, 3}, + }, + }, + { + name: "basic slice float64", + json: `{ + "sliceFloat64": [1.3,2.4,3.1] + }`, + expectedResult: slicesTestObject{ + sliceFloat64: []float64{1.3, 2.4, 3.1}, + }, + }, + { + name: "err slice float64", + json: `{ + "sliceFloat64": [1.3",2.4,3.1] + }`, + err: true, + }, + { + name: "err slice str", + json: `{ + "sliceString": [",""] + }`, + err: true, + }, + { + name: "err slice int", + json: `{ + "sliceInt": [1t,2,3] + }`, + err: true, + }, + { + name: "err slice bool", + json: `{ + "sliceBool": [truo,false] + }`, + err: true, + }, + } + + for _, testCase := range testCases { + t.Run( + testCase.name, + func(t *testing.T) { + dec := BorrowDecoder(strings.NewReader(testCase.json)) + var o slicesTestObject + err := dec.Decode(&o) + + 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, o) + }, + ) + } +} diff --git a/decode_sqlnull_test.go b/decode_sqlnull_test.go @@ -211,13 +211,13 @@ type SQLDecodeObject struct { func (s *SQLDecodeObject) UnmarshalJSONObject(dec *Decoder, k string) error { switch k { case "s": - return dec.SQLNullString(&s.S) + return dec.AddSQLNullString(&s.S) case "f": - return dec.SQLNullFloat64(&s.F) + return dec.AddSQLNullFloat64(&s.F) case "i": - return dec.SQLNullInt64(&s.I) + return dec.AddSQLNullInt64(&s.I) case "b": - return dec.SQLNullBool(&s.B) + return dec.AddSQLNullBool(&s.B) } return nil } @@ -231,6 +231,7 @@ func TestDecodeSQLNullKeys(t *testing.T) { name string json string expectedResult *SQLDecodeObject + err bool }{ { name: "basic all valid", @@ -358,6 +359,42 @@ func TestDecodeSQLNullKeys(t *testing.T) { }, }, }, + { + name: "err string key", + json: `{ + "s": "`, + err: true, + }, + { + name: "err float key", + json: `{ + "s": null, + "f": 1", + "i": null, + "b": null + }`, + err: true, + }, + { + name: "err int key", + json: `{ + "s": null, + "f": null, + "i": 1", + "b": null + }`, + err: true, + }, + { + name: "err bool key", + json: `{ + "s": null, + "f": null, + "i": null, + "b": tra + }`, + err: true, + }, } for _, testCase := range testCases { @@ -365,6 +402,12 @@ func TestDecodeSQLNullKeys(t *testing.T) { var o = &SQLDecodeObject{} var dec = NewDecoder(strings.NewReader(testCase.json)) var err = dec.Decode(o) + + if testCase.err { + require.NotNil(t, err) + return + } + require.Nil(t, err) require.Equal( t, diff --git a/decode_string_test.go b/decode_string_test.go @@ -702,6 +702,14 @@ func TestDecoderSkipEscapedStringError3(t *testing.T) { assert.IsType(t, InvalidJSONError(""), err, "err must be of type InvalidJSONError") } +func TestDecoderSkipEscapedStringError4(t *testing.T) { + dec := NewDecoder(strings.NewReader(`\u12`)) + defer dec.Release() + err := dec.skipEscapedString() + assert.NotNil(t, err, "Err must be nil") + assert.IsType(t, InvalidJSONError(""), err, "err must be of type InvalidJSONError") +} + func TestDecoderSkipStringError(t *testing.T) { dec := NewDecoder(strings.NewReader(`invalid`)) defer dec.Release() diff --git a/encode_slice.go b/encode_slice.go @@ -0,0 +1,113 @@ +package gojay + +// AddSliceString marshals the given []string s +func (enc *Encoder) AddSliceString(s []string) { + enc.SliceString(s) +} + +// SliceString marshals the given []string s +func (enc *Encoder) SliceString(s []string) { + enc.Array(EncodeArrayFunc(func(enc *Encoder) { + for _, str := range s { + enc.String(str) + } + })) +} + +// AddSliceStringKey marshals the given []string s +func (enc *Encoder) AddSliceStringKey(k string, s []string) { + enc.SliceStringKey(k, s) +} + +// SliceStringKey marshals the given []string s +func (enc *Encoder) SliceStringKey(k string, s []string) { + enc.ArrayKey(k, EncodeArrayFunc(func(enc *Encoder) { + for _, str := range s { + enc.String(str) + } + })) +} + +// AddSliceInt marshals the given []int s +func (enc *Encoder) AddSliceInt(s []int) { + enc.SliceInt(s) +} + +// SliceInt marshals the given []int s +func (enc *Encoder) SliceInt(s []int) { + enc.Array(EncodeArrayFunc(func(enc *Encoder) { + for _, i := range s { + enc.Int(i) + } + })) +} + +// AddSliceIntKey marshals the given []int s +func (enc *Encoder) AddSliceIntKey(k string, s []int) { + enc.SliceIntKey(k, s) +} + +// SliceIntKey marshals the given []int s +func (enc *Encoder) SliceIntKey(k string, s []int) { + enc.ArrayKey(k, EncodeArrayFunc(func(enc *Encoder) { + for _, i := range s { + enc.Int(i) + } + })) +} + +// AddSliceFloat64 marshals the given []float64 s +func (enc *Encoder) AddSliceFloat64(s []float64) { + enc.SliceFloat64(s) +} + +// SliceFloat64 marshals the given []float64 s +func (enc *Encoder) SliceFloat64(s []float64) { + enc.Array(EncodeArrayFunc(func(enc *Encoder) { + for _, i := range s { + enc.Float64(i) + } + })) +} + +// AddSliceFloat64Key marshals the given []float64 s +func (enc *Encoder) AddSliceFloat64Key(k string, s []float64) { + enc.SliceFloat64Key(k, s) +} + +// SliceFloat64Key marshals the given []float64 s +func (enc *Encoder) SliceFloat64Key(k string, s []float64) { + enc.ArrayKey(k, EncodeArrayFunc(func(enc *Encoder) { + for _, i := range s { + enc.Float64(i) + } + })) +} + +// AddSliceBool marshals the given []bool s +func (enc *Encoder) AddSliceBool(s []bool) { + enc.SliceBool(s) +} + +// SliceBool marshals the given []bool s +func (enc *Encoder) SliceBool(s []bool) { + enc.Array(EncodeArrayFunc(func(enc *Encoder) { + for _, i := range s { + enc.Bool(i) + } + })) +} + +// AddSliceBoolKey marshals the given []bool s +func (enc *Encoder) AddSliceBoolKey(k string, s []bool) { + enc.SliceBoolKey(k, s) +} + +// SliceBoolKey marshals the given []bool s +func (enc *Encoder) SliceBoolKey(k string, s []bool) { + enc.ArrayKey(k, EncodeArrayFunc(func(enc *Encoder) { + for _, i := range s { + enc.Bool(i) + } + })) +} diff --git a/encode_slice_test.go b/encode_slice_test.go @@ -0,0 +1,187 @@ +package gojay + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func (s *slicesTestObject) MarshalJSONObject(enc *Encoder) { + enc.AddSliceStringKey("sliceString", s.sliceString) + enc.AddSliceIntKey("sliceInt", s.sliceInt) + enc.AddSliceFloat64Key("sliceFloat64", s.sliceFloat64) + enc.AddSliceBoolKey("sliceBool", s.sliceBool) +} + +func (s *slicesTestObject) IsNil() bool { + return s == nil +} + +func TestEncodeSlices(t *testing.T) { + testCases := []struct { + name string + json string + obj slicesTestObject + }{ + { + name: "basic slice string", + json: `{ + "sliceString": ["foo","bar"], + "sliceInt": [], + "sliceFloat64": [], + "sliceBool": [] + }`, + obj: slicesTestObject{ + sliceString: []string{"foo", "bar"}, + }, + }, + { + name: "basic slice bool", + json: `{ + "sliceString": [], + "sliceInt": [], + "sliceFloat64": [], + "sliceBool": [true,false] + }`, + obj: slicesTestObject{ + sliceBool: []bool{true, false}, + }, + }, + { + name: "basic slice int", + json: `{ + "sliceString": [], + "sliceFloat64": [], + "sliceInt": [1,2,3], + "sliceBool": [] + }`, + obj: slicesTestObject{ + sliceInt: []int{1, 2, 3}, + }, + }, + { + name: "basic slice float64", + json: `{ + "sliceString": [], + "sliceFloat64": [1.3,2.4,3.1], + "sliceInt": [], + "sliceBool": [] + }`, + obj: slicesTestObject{ + sliceFloat64: []float64{1.3, 2.4, 3.1}, + }, + }, + } + + for _, testCase := range testCases { + t.Run( + testCase.name, + func(t *testing.T) { + b := strings.Builder{} + enc := BorrowEncoder(&b) + err := enc.Encode(&testCase.obj) + require.Nil(t, err, "err should be nil") + require.JSONEq(t, testCase.json, b.String()) + }, + ) + } +} + +type testSliceSliceString [][]string + +func (t testSliceSliceString) MarshalJSONArray(enc *Encoder) { + for _, s := range t { + enc.AddSliceString(s) + } +} + +func (t testSliceSliceString) IsNil() bool { + return t == nil +} + +type testSliceSliceBool [][]bool + +func (t testSliceSliceBool) MarshalJSONArray(enc *Encoder) { + for _, s := range t { + enc.AddSliceBool(s) + } +} + +func (t testSliceSliceBool) IsNil() bool { + return t == nil +} + +type testSliceSliceInt [][]int + +func (t testSliceSliceInt) MarshalJSONArray(enc *Encoder) { + for _, s := range t { + enc.AddSliceInt(s) + } +} + +func (t testSliceSliceInt) IsNil() bool { + return t == nil +} + +type testSliceSliceFloat64 [][]float64 + +func (t testSliceSliceFloat64) MarshalJSONArray(enc *Encoder) { + for _, s := range t { + enc.AddSliceFloat64(s) + } +} + +func (t testSliceSliceFloat64) IsNil() bool { + return t == nil +} + +func TestEncodeSliceSlices(t *testing.T) { + testCases := []struct { + name string + s MarshalerJSONArray + json string + }{ + { + name: "slice of strings", + s: testSliceSliceString{ + []string{"foo", "bar"}, + }, + json: `[["foo","bar"]]`, + }, + { + name: "slice of ints", + s: testSliceSliceInt{ + []int{1, 2}, + }, + json: `[[1,2]]`, + }, + { + name: "slice of float", + s: testSliceSliceFloat64{ + []float64{1.1, 1.2}, + }, + json: `[[1.1,1.2]]`, + }, + { + name: "slice of bool", + s: testSliceSliceBool{ + []bool{true, false}, + }, + json: `[[true,false]]`, + }, + } + + for _, testCase := range testCases { + t.Run( + testCase.name, + func(t *testing.T) { + b := strings.Builder{} + enc := BorrowEncoder(&b) + err := enc.Encode(testCase.s) + require.Nil(t, err, "err should be nil") + require.JSONEq(t, testCase.json, b.String()) + }, + ) + } +}