gojay

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

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:
Mdecode.go | 2++
Adecode_embedded_json.go | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adecode_embedded_json_test.go | 102+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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`) +}