commit 4c9828d686c0ea439ab0304b6f1c57760cc70e13
parent a5068885b61dc3bbf5c753d34a3e2647772d3a00
Author: Salim Afiune <afiune@chef.io>
Date: Wed, 22 Aug 2018 15:10:25 +0200
Adds DecodeInterface for interface decoding
Temporal solution to the unmarshaling of interfaces in Go
Signed-off-by: Salim Afiune <afiune@chef.io>
Diffstat:
6 files changed, 300 insertions(+), 4 deletions(-)
diff --git a/decode.go b/decode.go
@@ -227,6 +227,8 @@ func (dec *Decoder) Decode(v interface{}) error {
_, err = dec.decodeArray(vt)
case *EmbeddedJSON:
err = dec.decodeEmbeddedJSON(vt)
+ case *interface{}:
+ err = dec.decodeInterface(vt)
default:
return InvalidUnmarshalError(fmt.Sprintf(invalidUnmarshalErrorMsg, reflect.TypeOf(vt).String()))
}
@@ -333,6 +335,11 @@ func (dec *Decoder) AddArray(v UnmarshalerJSONArray) error {
return dec.Array(v)
}
+// AddInterface decodes the next key to a interface{}.
+func (dec *Decoder) AddInterface(v *interface{}) error {
+ return dec.Interface(v)
+}
+
// Int decodes the next key to an *int.
// If next key value overflows int, an InvalidUnmarshalError error will be returned.
func (dec *Decoder) Int(v *int) error {
@@ -527,6 +534,16 @@ func (dec *Decoder) Array(value UnmarshalerJSONArray) error {
return nil
}
+// Interface decodes the next key to an interface{}.
+func (dec *Decoder) Interface(value *interface{}) error {
+ err := dec.decodeInterface(value)
+ if err != nil {
+ return err
+ }
+ dec.called |= 1
+ return nil
+}
+
// Non exported
func isDigit(b byte) bool {
diff --git a/decode_interface.go b/decode_interface.go
@@ -0,0 +1,103 @@
+package gojay
+
+// TODO @afiune for now we are using the standard json unmarshaling but in
+// the future it would be great to implement one here inside this repo
+import (
+ "encoding/json"
+)
+
+// DecodeInterface reads the next JSON-encoded value from its input and stores it in the value pointed to by i.
+//
+// i must be an interface poiter
+func (dec *Decoder) DecodeInterface(i *interface{}) error {
+ if dec.isPooled == 1 {
+ panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder"))
+ }
+ err := dec.decodeInterface(i)
+ return err
+}
+
+func (dec *Decoder) decodeInterface(i *interface{}) error {
+ start, end, err := dec.getObject()
+ if err != nil {
+ dec.cursor = start
+ return err
+ }
+
+ object := dec.data[start:end]
+ if err = json.Unmarshal(object, i); err != nil {
+ return err
+ }
+
+ dec.cursor = end
+ return nil
+}
+
+// @afiune Maybe return the type as well?
+func (dec *Decoder) getObject() (start int, end int, err error) {
+ // start cursor
+ start = dec.cursor
+ for ; dec.cursor < dec.length || dec.read(); dec.cursor++ {
+ switch dec.data[dec.cursor] {
+ case ' ', '\n', '\t', '\r', ',':
+ continue
+ // is null
+ case 'n':
+ dec.cursor++
+ err = dec.assertNull()
+ if err != nil {
+ return
+ }
+ end = dec.cursor
+ dec.cursor++
+ return
+ case 't':
+ dec.cursor++
+ err = dec.assertTrue()
+ if err != nil {
+ return
+ }
+ end = dec.cursor
+ dec.cursor++
+ return
+ // is false
+ case 'f':
+ dec.cursor++
+ err = dec.assertFalse()
+ if err != nil {
+ return
+ }
+ end = dec.cursor
+ dec.cursor++
+ return
+ // is an object
+ case '{':
+ dec.cursor++
+ end, err = dec.skipObject()
+ dec.cursor = end
+ return
+ // is string
+ case '"':
+ dec.cursor++
+ start, end, err = dec.getString()
+ start--
+ dec.cursor = end
+ return
+ // is array
+ case '[':
+ dec.cursor++
+ end, err = dec.skipArray()
+ dec.cursor = end
+ return
+ case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-':
+ end, err = dec.skipNumber()
+ dec.cursor = end
+ return
+ default:
+ err = dec.raiseInvalidJSONErr(dec.cursor)
+ return
+ }
+ }
+ err = dec.raiseInvalidJSONErr(dec.cursor)
+ return
+}
diff --git a/decode_interface_test.go b/decode_interface_test.go
@@ -0,0 +1,169 @@
+package gojay
+
+import (
+ "encoding/json"
+ "strings"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestDecodeInterfaceBasic(t *testing.T) {
+ testCases := []struct {
+ name string
+ json string
+ expectedResult interface{}
+ err bool
+ errType interface{}
+ skipCheckResult bool
+ }{
+ {
+ name: "array",
+ json: `[1,2,3]`,
+ expectedResult: []interface{}([]interface{}{float64(1), float64(2), float64(3)}),
+ err: false,
+ },
+ {
+ name: "object",
+ json: `{"testStr": "hello world!"}`,
+ expectedResult: map[string]interface{}(map[string]interface{}{"testStr": "hello world!"}),
+ err: false,
+ },
+ {
+ name: "array-error",
+ json: `["h""o","l","a"]`,
+ err: true,
+ errType: &json.SyntaxError{},
+ skipCheckResult: true,
+ },
+ {
+ name: "object-error",
+ json: `{"testStr" "hello world!"}`,
+ err: true,
+ errType: &json.SyntaxError{},
+ skipCheckResult: true,
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ var i interface{}
+ dec := BorrowDecoder(strings.NewReader(testCase.json))
+ defer dec.Release()
+ err := dec.Decode(&i)
+ if testCase.err {
+ t.Log(err)
+ assert.NotNil(t, err, "err should not be nil")
+ if testCase.errType != nil {
+ assert.IsType(t, testCase.errType, err, "err should be of the given type")
+ }
+ return
+ }
+ assert.Nil(t, err, "err should be nil")
+ if !testCase.skipCheckResult {
+ assert.Equal(t, testCase.expectedResult, i, "value at given index should be the same as expected results")
+ }
+ })
+ }
+}
+
+func TestDecodeInterfaceObject(t *testing.T) {
+ testCases := []struct {
+ name string
+ json string
+ expectedResult testObject
+ err bool
+ errType interface{}
+ skipCheckResult bool
+ }{
+ {
+ name: "basic-array",
+ json: `{
+ "testStr": "hola",
+ "testInterface": ["h","o","l","a"],
+ }`,
+ expectedResult: testObject{
+ testStr: "hola",
+ testInterface: []interface{}([]interface{}{"h", "o", "l", "a"}),
+ },
+ err: false,
+ },
+ {
+ name: "basic-string",
+ json: `{
+ "testInterface": "漢字",
+ }`,
+ expectedResult: testObject{
+ testInterface: interface{}("漢字"),
+ },
+ err: false,
+ },
+ {
+ name: "basic-interface",
+ json: `{
+ "testInterface": {
+ "string": "prost"
+ },
+ }`,
+ expectedResult: testObject{
+ testInterface: map[string]interface{}{"string": "prost"},
+ },
+ err: false,
+ },
+ {
+ name: "complex-interface",
+ json: `{
+ "testInterface": {
+ "number": 1988,
+ "string": "prost",
+ "array": ["h","o","l","a"],
+ "object": {
+ "k": "v",
+ "a": [1,2,3]
+ },
+ "array-of-objects": [
+ {"k": "v"},
+ {"a": "b"}
+ ]
+ },
+ }`,
+ expectedResult: testObject{
+ testInterface: map[string]interface{}{
+ "array-of-objects": []interface{}{
+ map[string]interface{}{"k": "v"},
+ map[string]interface{}{"a": "b"},
+ },
+ "number": float64(1988),
+ "string": "prost",
+ "array": []interface{}{"h", "o", "l", "a"},
+ "object": map[string]interface{}{
+ "k": "v",
+ "a": []interface{}{float64(1), float64(2), float64(3)},
+ },
+ },
+ },
+ err: false,
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ s := testObject{}
+ dec := BorrowDecoder(strings.NewReader(testCase.json))
+ defer dec.Release()
+ err := dec.Decode(&s)
+ if testCase.err {
+ t.Log(err)
+ assert.NotNil(t, err, "err should not be nil")
+ if testCase.errType != nil {
+ assert.IsType(t, testCase.errType, err, "err should be of the given type")
+ }
+ return
+ }
+ assert.Nil(t, err, "err should be nil")
+ if !testCase.skipCheckResult {
+ assert.Equal(t, testCase.expectedResult, s, "value at given index should be the same as expected results")
+ }
+ })
+ }
+}
diff --git a/encode_interface_test.go b/encode_interface_test.go
@@ -112,7 +112,7 @@ var encoderTestCases = []struct {
},
},
{
- v: &testObject{"漢字", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.1, 1.1, true, &testObject{}, testSliceInts{}},
+ v: &testObject{"漢字", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.1, 1.1, true, &testObject{}, testSliceInts{}, []interface{}{"h", "o", "l", "a"}},
expectations: func(t *testing.T, b string, err error) {
assert.Nil(t, err, "err should be nil")
assert.Equal(t, `{"testStr":"漢字","testInt":1,"testInt64":1,"testInt32":1,"testInt16":1,"testInt8":1,"testUint64":1,"testUint32":1,"testUint16":1,"testUint8":1,"testFloat64":1.1,"testFloat32":1.1,"testBool":true}`, string(b), `string(b) should equal {"testStr":"漢字","testInt":1,"testInt64":1,"testInt32":1,"testInt16":1,"testInt8":1,"testUint64":1,"testUint32":1,"testUint16":1,"testUint8":1,"testFloat64":1.1,"testFloat32":1.1,"testBool":true}`)
diff --git a/encode_object_test.go b/encode_object_test.go
@@ -83,7 +83,7 @@ func TestEncoderObjectEncodeAPI(t *testing.T) {
t.Run("encode-basic", func(t *testing.T) {
builder := &strings.Builder{}
enc := NewEncoder(builder)
- err := enc.EncodeObject(&testObject{"漢字", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.1, 1.1, true, &testObject{}, testSliceInts{}})
+ err := enc.EncodeObject(&testObject{"漢字", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.1, 1.1, true, &testObject{}, testSliceInts{}, interface{}("test")})
assert.Nil(t, err, "Error should be nil")
assert.Equal(
t,
@@ -96,7 +96,7 @@ func TestEncoderObjectEncodeAPI(t *testing.T) {
func TestEncoderObjectMarshalAPI(t *testing.T) {
t.Run("marshal-basic", func(t *testing.T) {
- r, err := Marshal(&testObject{"漢字", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.1, 1.1, true, &testObject{}, testSliceInts{}})
+ r, err := Marshal(&testObject{"漢字", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.1, 1.1, true, &testObject{}, testSliceInts{}, []interface{}{"h", "o", "l", "a"}})
assert.Nil(t, err, "Error should be nil")
assert.Equal(
t,
diff --git a/gojay_test.go b/gojay_test.go
@@ -16,6 +16,7 @@ type testObject struct {
testBool bool
testSubObject *testObject
testSubArray testSliceInts
+ testInterface interface{}
}
// make sure it implements interfaces
@@ -70,12 +71,14 @@ func (t *testObject) UnmarshalJSONObject(dec *Decoder, k string) error {
return dec.AddFloat32(&t.testFloat32)
case "testBool":
return dec.AddBool(&t.testBool)
+ case "testInterface":
+ return dec.AddInterface(&t.testInterface)
}
return nil
}
func (t *testObject) NKeys() int {
- return 13
+ return 14
}
type testObject0Keys struct {
@@ -94,6 +97,7 @@ type testObject0Keys struct {
testBool bool
testSubObject *testObject0Keys
testSubArray testSliceInts
+ testInterface interface{}
}
// make sure it implements interfaces
@@ -118,6 +122,7 @@ func (t *testObject0Keys) MarshalJSONObject(enc *Encoder) {
enc.AddFloatKey("testFloat64", t.testFloat64)
enc.AddFloat32Key("testFloat32", t.testFloat32)
enc.AddBoolKey("testBool", t.testBool)
+ enc.AddInterfaceKey("testInterface", t.testInterface)
}
func (t *testObject0Keys) UnmarshalJSONObject(dec *Decoder, k string) error {
@@ -148,6 +153,8 @@ func (t *testObject0Keys) UnmarshalJSONObject(dec *Decoder, k string) error {
return dec.AddFloat32(&t.testFloat32)
case "testBool":
return dec.AddBool(&t.testBool)
+ case "testInterface":
+ return dec.AddInterface(&t.testInterface)
}
return nil
}