gojay

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

commit bd731ef75d7fbb57cad637370b64f167aa5ead0c
parent 75dba3be66f280626f447c63e65179417b651429
Author: francoispqt <francois@parquet.ninja>
Date:   Sun, 12 Aug 2018 01:15:44 +0800

Merge branch 'lorenzo-stoakes-master' into version/v1.1.2

Diffstat:
Mdecode_array_test.go | 4++--
Mdecode_number_float.go | 4++--
Mdecode_number_float_test.go | 10++++++++++
Mdecode_object_test.go | 18+++++++++---------
Mdecode_string.go | 165++++++++++++++++++++++---------------------------------------------------------
Mdecode_string_test.go | 42+++++++++++++++++++++---------------------
6 files changed, 90 insertions(+), 153 deletions(-)

diff --git a/decode_array_test.go b/decode_array_test.go @@ -112,12 +112,12 @@ func TestSliceStrings(t *testing.T) { }, { name: "basic-test", - json: `["hello world", "hey" , "foo","bar \\n escape"]`, + json: `["hello world", "hey" , "foo","bar \n escape"]`, expectedResult: testSliceStrings{"hello world", "hey", "foo", "bar \n escape"}, }, { name: "basic-test", - json: `["hello world", "hey" , null,"bar \\n escape"]`, + json: `["hello world", "hey" , null,"bar \n escape"]`, expectedResult: testSliceStrings{"hello world", "hey", "", "bar \n escape"}, }, { diff --git a/decode_number_float.go b/decode_number_float.go @@ -53,7 +53,7 @@ func (dec *Decoder) getFloatNegative() (float64, error) { // look for following numbers for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { switch dec.data[dec.cursor] { - case '1', '2', '3', '4', '5', '6', '7', '8', '9': + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': return dec.getFloat() default: return 0, dec.raiseInvalidJSONErr(dec.cursor) @@ -203,7 +203,7 @@ func (dec *Decoder) getFloat32Negative() (float32, error) { // look for following numbers for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { switch dec.data[dec.cursor] { - case '1', '2', '3', '4', '5', '6', '7', '8', '9': + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': return dec.getFloat32() default: return 0, dec.raiseInvalidJSONErr(dec.cursor) diff --git a/decode_number_float_test.go b/decode_number_float_test.go @@ -195,6 +195,11 @@ func TestDecoderFloat64(t *testing.T) { expectedResult: -788.76, }, { + name: "basic-float3", + json: "-0.1234", + expectedResult: -0.1234, + }, + { name: "basic-exp-too-big", json: "1e10000000000 ", expectedResult: 0, @@ -539,6 +544,11 @@ func TestDecoderFloat32(t *testing.T) { expectedResult: -788.76, }, { + name: "basic-float3", + json: "-0.1234", + expectedResult: -0.1234, + }, + { name: "error", json: "83zez4", expectedResult: 0, diff --git a/decode_object_test.go b/decode_object_test.go @@ -877,14 +877,14 @@ func TestDecodeObjectComplex(t *testing.T) { json: `{ "testSubObject": { "testStr": "some string", - "testInt":124465, - "testUint16":120, - "testUint8":15, - "testInt16":-135, + "testInt":124465, + "testUint16":120, + "testUint8":15, + "testInt16":-135, "testInt8":-23 }, "testSubSliceInts": [1,2,3,4,5], - "testStr": "some \\n string" + "testStr": "some \n string" }`, expectedResult: testObjectComplex{ testSubObject: &testObject{ @@ -902,7 +902,7 @@ func TestDecodeObjectComplex(t *testing.T) { }, { name: "complex-json-err", - json: `{"testSubObject":{"testStr":"some string,"testInt":124465,"testUint16":120, "testUint8":15,"testInt16":-135,"testInt8":-23},"testSubSliceInts":[1,2],"testStr":"some \\n string"}`, + json: `{"testSubObject":{"testStr":"some string,"testInt":124465,"testUint16":120, "testUint8":15,"testInt16":-135,"testInt8":-23},"testSubSliceInts":[1,2],"testStr":"some \n string"}`, expectedResult: testObjectComplex{ testSubObject: &testObject{}, }, @@ -1012,9 +1012,9 @@ func TestDecodeObjectNull(t *testing.T) { var jsonComplex = []byte(`{ "test": "{\"test\":\"1\",\"test1\":2}", - "test2\\n": "\\\\\\\\\\n", + "test2\n": "\\\\\\\\\n", "testArrSkip": ["testString with escaped \\\" quotes"], - "testSkipString": "skip \\ string with \\n escaped char \" ", + "testSkipString": "skip \\ string with \n escaped char \" ", "testSkipObject": { "testSkipSubObj": { "test": "test" @@ -1028,7 +1028,7 @@ var jsonComplex = []byte(`{ "testSkipBoolNull": null, "testSub": { "test": "{\"test\":\"1\",\"test1\":2}", - "test2\\n": "[1,2,3]", + "test2\n": "[1,2,3]", "test3": 1, "testObjSkip": { "test": "test string with escaped \" quotes" diff --git a/decode_string.go b/decode_string.go @@ -52,127 +52,54 @@ func (dec *Decoder) decodeString(v *string) error { } func (dec *Decoder) parseEscapedString() error { - // know where to stop slash - start := dec.cursor - for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { - if dec.data[dec.cursor] != '\\' { - d := dec.data[dec.cursor] - dec.cursor = dec.cursor + 1 - nSlash := dec.cursor - start - switch d { - case '"': - // nSlash must be odd - if nSlash&1 != 1 { - return dec.raiseInvalidJSONErr(dec.cursor) - } - diff := (nSlash - 1) >> 1 - dec.data = append(dec.data[:start+diff-1], dec.data[dec.cursor-1:]...) - dec.length = len(dec.data) - dec.cursor -= nSlash - diff - return nil - case 'u': - if nSlash&1 == 0 { - diff := nSlash >> 1 - dec.data = append(dec.data[:start+diff-1], dec.data[dec.cursor-1:]...) - dec.length = len(dec.data) - dec.cursor -= nSlash - diff - return nil - } - start := dec.cursor - 2 - ((nSlash - 1) >> 1) - str, err := dec.parseUnicode() - if err != nil { - dec.err = err - return err - } - diff := dec.cursor - start - dec.data = append(append(dec.data[:start], str...), dec.data[dec.cursor:]...) - dec.length = len(dec.data) - dec.cursor = dec.cursor - diff + len(str) - return nil - case 'b': - // number of slash must be even - // if is odd number of slashes - // divide nSlash - 1 by 2 and leave last one - // else divide nSlash by 2 and leave the letter - if nSlash&1 != 0 { - return dec.raiseInvalidJSONErr(dec.cursor) - } - var diff int - diff = nSlash >> 1 - dec.data = append(append(dec.data[:start+diff-2], '\b'), dec.data[dec.cursor:]...) - dec.length = len(dec.data) - dec.cursor -= nSlash - diff + 1 - return nil - case 'f': - // number of slash must be even - // if is odd number of slashes - // divide nSlash - 1 by 2 and leave last one - // else divide nSlash by 2 and leave the letter - if nSlash&1 != 0 { - return dec.raiseInvalidJSONErr(dec.cursor) - } - var diff int - diff = nSlash >> 1 - dec.data = append(append(dec.data[:start+diff-2], '\f'), dec.data[dec.cursor:]...) - dec.length = len(dec.data) - dec.cursor -= nSlash - diff + 1 - return nil - case 'n': - // number of slash must be even - // if is odd number of slashes - // divide nSlash - 1 by 2 and leave last one - // else divide nSlash by 2 and leave the letter - if nSlash&1 != 0 { - return dec.raiseInvalidJSONErr(dec.cursor) - } - var diff int - diff = nSlash >> 1 - dec.data = append(append(dec.data[:start+diff-2], '\n'), dec.data[dec.cursor:]...) - dec.length = len(dec.data) - dec.cursor -= nSlash - diff + 1 - return nil - case 'r': - // number of slash must be even - // if is odd number of slashes - // divide nSlash - 1 by 2 and leave last one - // else divide nSlash by 2 and leave the letter - if nSlash&1 != 0 { - return dec.raiseInvalidJSONErr(dec.cursor) - } - var diff int - diff = nSlash >> 1 - dec.data = append(append(dec.data[:start+diff-2], '\r'), dec.data[dec.cursor:]...) - dec.length = len(dec.data) - dec.cursor -= nSlash - diff + 1 - return nil - case 't': - // number of slash must be even - // if is odd number of slashes - // divide nSlash - 1 by 2 and leave last one - // else divide nSlash by 2 and leave the letter - if nSlash&1 != 0 { - return dec.raiseInvalidJSONErr(dec.cursor) - } - var diff int - diff = nSlash >> 1 - dec.data = append(append(dec.data[:start+diff-2], '\t'), dec.data[dec.cursor:]...) - dec.length = len(dec.data) - dec.cursor -= nSlash - diff + 1 - return nil - default: - // nSlash must be even - if nSlash&1 == 1 { - return dec.raiseInvalidJSONErr(dec.cursor) - } - diff := nSlash >> 1 - dec.data = append(dec.data[:start+diff-1], dec.data[dec.cursor-1:]...) - dec.length = len(dec.data) - dec.cursor -= (nSlash - diff) - return nil - } + if dec.cursor >= dec.length && !dec.read() { + return dec.raiseInvalidJSONErr(dec.cursor) + } + + switch dec.data[dec.cursor] { + case '"': + dec.data[dec.cursor] = '"' + case '\\': + dec.data[dec.cursor] = '\\' + case '/': + dec.data[dec.cursor] = '/' + case 'b': + dec.data[dec.cursor] = '\b' + case 'f': + dec.data[dec.cursor] = '\f' + case 'n': + dec.data[dec.cursor] = '\n' + case 'r': + dec.data[dec.cursor] = '\r' + case 't': + dec.data[dec.cursor] = '\t' + case 'u': + start := dec.cursor + dec.cursor++ + str, err := dec.parseUnicode() + if err != nil { + return err } + + diff := dec.cursor - start + dec.data = append(append(dec.data[:start-1], str...), dec.data[dec.cursor:]...) + dec.length = len(dec.data) + dec.cursor += len(str) - diff - 1 + + return nil + default: + return dec.raiseInvalidJSONErr(dec.cursor) } - return dec.raiseInvalidJSONErr(dec.cursor) + + // Truncate the previous backslash character, and the + dec.data = append(dec.data[:dec.cursor-1], dec.data[dec.cursor:]...) + dec.length-- + + // Since we've lost a character, our dec.cursor offset is now + // 1 past the escaped character which is precisely where we + // want it. + + return nil } func (dec *Decoder) getString() (int, int, error) { diff --git a/decode_string_test.go b/decode_string_test.go @@ -44,56 +44,56 @@ func TestDecoderString(t *testing.T) { { name: "escape-control-char", json: `"\n"`, - expectedResult: "", - err: true, + expectedResult: "\n", + err: false, }, { name: "escape-control-char", json: `"\\n"`, - expectedResult: "\n", + expectedResult: `\n`, err: false, }, { name: "escape-control-char", json: `"\t"`, - expectedResult: "", - err: true, + expectedResult: "\t", + err: false, }, { name: "escape-control-char", json: `"\\t"`, - expectedResult: "\t", + expectedResult: `\t`, err: false, }, { name: "escape-control-char", json: `"\b"`, - expectedResult: "", - err: true, + expectedResult: "\b", + err: false, }, { name: "escape-control-char", json: `"\\b"`, - expectedResult: "\b", + expectedResult: `\b`, err: false, }, { name: "escape-control-char", json: `"\f"`, - expectedResult: "", - err: true, + expectedResult: "\f", + err: false, }, { name: "escape-control-char", json: `"\\f"`, - expectedResult: "\f", + expectedResult: `\f`, err: false, }, { name: "escape-control-char", json: `"\r"`, - expectedResult: "", - err: true, + expectedResult: "\r", + err: false, }, { name: "escape-control-char", @@ -104,7 +104,7 @@ func TestDecoderString(t *testing.T) { { name: "escape-control-char", json: `"\\r"`, - expectedResult: "\r", + expectedResult: `\r`, err: false, }, { @@ -228,31 +228,31 @@ func TestDecoderString(t *testing.T) { }, { name: "escape quote err2", - json: `"test string \\t escaped"`, + json: `"test string \t escaped"`, expectedResult: "test string \t escaped", err: false, }, { name: "escape quote err2", - json: `"test string \\r escaped"`, + json: `"test string \r escaped"`, expectedResult: "test string \r escaped", err: false, }, { name: "escape quote err2", - json: `"test string \\b escaped"`, + json: `"test string \b escaped"`, expectedResult: "test string \b escaped", err: false, }, { name: "escape quote err", - json: `"test string \\n escaped"`, + json: `"test string \n escaped"`, expectedResult: "test string \n escaped", err: false, }, { name: "escape quote err", - json: `"test string \\" escaped"`, + json: `"test string \\\" escaped`, expectedResult: ``, err: true, errType: InvalidJSONError(""), @@ -273,7 +273,7 @@ func TestDecoderString(t *testing.T) { }, { name: "string-complex", - json: ` "string with spaces and \"escape\"d \"quotes\" and escaped line returns \\n and escaped \\\\ escaped char"`, + json: ` "string with spaces and \"escape\"d \"quotes\" and escaped line returns \n and escaped \\\\ escaped char"`, expectedResult: "string with spaces and \"escape\"d \"quotes\" and escaped line returns \n and escaped \\\\ escaped char", }, }