commit d3845d8d57a705dec739b5c831eefdb14c6cb749
parent 514aec19fd89e723006ae08b3dcc094a206e85d4
Author: francoispqt <francois@parquet.ninja>
Date: Sun, 6 May 2018 12:32:16 +0800
add support for decoding embedded JSON
Diffstat:
3 files changed, 167 insertions(+), 0 deletions(-)
diff --git a/decode.go b/decode.go
@@ -190,6 +190,8 @@ func (dec *Decoder) Decode(v interface{}) error {
case UnmarshalerArray:
_, err := dec.decodeArray(vt)
return err
+ case *EmbeddedJSON: // <----------------------------------------- NEW
+ return dec.decodeEmbeddedJSON(vt) // <------------ NEW
default:
return InvalidUnmarshalError(fmt.Sprintf(invalidUnmarshalErrorMsg, reflect.TypeOf(vt).String()))
}
diff --git a/decode_embedded_json.go b/decode_embedded_json.go
@@ -0,0 +1,63 @@
+package gojay
+
+// EmbeddedJSON is a raw encoded JSON value.
+// It can be used to delay JSON decoding or precompute a JSON encoding.
+type EmbeddedJSON []byte
+
+func (dec *Decoder) decodeEmbeddedJSON(ej *EmbeddedJSON) error {
+ var err error
+ if ej == nil {
+ return InvalidUnmarshalError("Invalid nil pointer given")
+ }
+ var beginOfEmbeddedJSON int
+ for ; dec.cursor < dec.length || dec.read(); dec.cursor++ {
+ switch dec.data[dec.cursor] {
+ case ' ', '\n', '\t', '\r', ',':
+ continue
+ // is null
+ case 'n', 't':
+ beginOfEmbeddedJSON = dec.cursor
+ dec.cursor = dec.cursor + 4
+ // is false
+ case 'f':
+ beginOfEmbeddedJSON = dec.cursor
+ dec.cursor = dec.cursor + 5
+ // is an object
+ case '{':
+ beginOfEmbeddedJSON = dec.cursor
+ dec.cursor = dec.cursor + 1
+ dec.cursor, err = dec.skipObject()
+ // is string
+ case '"':
+ beginOfEmbeddedJSON = dec.cursor
+ dec.cursor = dec.cursor + 1
+ err = dec.skipString() // why no new dec.cursor in result?
+ // is array
+ case '[':
+ beginOfEmbeddedJSON = dec.cursor
+ dec.cursor = dec.cursor + 1
+ dec.cursor, err = dec.skipArray()
+ case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-':
+ beginOfEmbeddedJSON = dec.cursor
+ dec.cursor, err = dec.skipNumber()
+ }
+ break
+ }
+ if err == nil {
+ if dec.cursor-1 > beginOfEmbeddedJSON {
+ *ej = append(*ej, dec.data[beginOfEmbeddedJSON:dec.cursor]...)
+ }
+ }
+ return err
+}
+
+// AddEmbeddedJSON adds an EmbeddedsJSON to the value pointed by v.
+// It can be used to delay JSON decoding or precompute a JSON encoding.
+func (dec *Decoder) AddEmbeddedJSON(v *EmbeddedJSON) error {
+ err := dec.decodeEmbeddedJSON(v)
+ if err != nil {
+ return err
+ }
+ dec.called |= 1
+ return nil
+}
diff --git a/decode_embedded_json_test.go b/decode_embedded_json_test.go
@@ -0,0 +1,102 @@
+package gojay
+
+import (
+ "strings"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+type Request struct {
+ id string
+ method string
+ params EmbeddedJSON
+ more int
+}
+
+func (r *Request) UnmarshalObject(dec *Decoder, key string) error {
+ switch key {
+ case "id":
+ return dec.AddString(&r.id)
+ case "method":
+ return dec.AddString(&r.method)
+ case "params":
+ return dec.AddEmbeddedJSON(&r.params)
+ case "more":
+ dec.AddInt(&r.more)
+ }
+ return nil
+}
+
+func (r *Request) NKeys() int {
+ return 4
+}
+
+func TestDecodeEmbeddedJSON(t *testing.T) {
+ testCases := []struct {
+ name string
+ json []byte
+ expectedEmbedded string
+ }{
+ {
+ name: "decode-basic-string",
+ json: []byte(`{"id":"someid","method":"getmydata","params":"raw data", "more":123}`),
+ expectedEmbedded: `"raw data"`,
+ },
+ {
+ name: "decode-basic-int",
+ json: []byte(`{"id":"someid","method":"getmydata","params":12345, "more":123}`),
+ expectedEmbedded: `12345`,
+ },
+ {
+ name: "decode-basic-int",
+ json: []byte(`{"id":"someid","method":"getmydata","params":true, "more":123}`),
+ expectedEmbedded: `true`,
+ },
+ {
+ name: "decode-basic-int",
+ json: []byte(`{"id":"someid","method":"getmydata","params": false, "more":123}`),
+ expectedEmbedded: `false`,
+ },
+ {
+ name: "decode-basic-int",
+ json: []byte(`{"id":"someid","method":"getmydata","params":null, "more":123}`),
+ expectedEmbedded: `null`,
+ },
+ {
+ name: "decode-basic-object",
+ json: []byte(`{"id":"someid","method":"getmydata","params":{"example":"of raw data"}, "more":123}`),
+ expectedEmbedded: `{"example":"of raw data"}`,
+ },
+ {
+ name: "decode-basic-object",
+ json: []byte(`{"id":"someid","method":"getmydata","params":[1,2,3], "more":123}`),
+ expectedEmbedded: `[1,2,3]`,
+ },
+ }
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ req := &Request{}
+ Unmarshal(testCase.json, req)
+ t.Log(req)
+ t.Log(string(req.params))
+ assert.Equal(t, testCase.expectedEmbedded, string(req.params), "r.params should be equal to expectedEmbeddedResult")
+ })
+ }
+}
+
+func TestDecodeEmbeededJSONNil(t *testing.T) {
+ dec := BorrowDecoder(strings.NewReader(`"bar"`))
+ var ej *EmbeddedJSON
+ err := dec.decodeEmbeddedJSON(ej)
+ assert.NotNil(t, err, `err should not be nil a nil pointer is given`)
+ assert.IsType(t, InvalidUnmarshalError(""), err, `err should not be of type InvalidUnmarshalError`)
+}
+
+func TestDecodeEmbeededJSONNil2(t *testing.T) {
+ dec := BorrowDecoder(strings.NewReader(`"bar"`))
+ var ej *EmbeddedJSON
+ err := dec.AddEmbeddedJSON(ej)
+ assert.NotNil(t, err, `err should not be nil a nil pointer is given`)
+ assert.IsType(t, InvalidUnmarshalError(""), err, `err should not be of type InvalidUnmarshalError`)
+}