diff --git a/iter_str.go b/iter_str.go index adc487ea..d464b54b 100644 --- a/iter_str.go +++ b/iter_str.go @@ -124,25 +124,36 @@ func (iter *Iterator) ReadStringAsSlice() (ret []byte) { ret = iter.buf[iter.head:i] iter.head = i + 1 return ret + } else if iter.buf[i] == '\\' { + // Contains escape sequences, need slow path + break } } - readLen := iter.tail - iter.head - copied := make([]byte, readLen, readLen*2) - copy(copied, iter.buf[iter.head:iter.tail]) - iter.head = iter.tail - for iter.Error == nil { - c := iter.readByte() - if c == '"' { - return copied - } - copied = append(copied, c) - } - return copied + return iter.readStringAsSliceSlowPath() } iter.ReportError("ReadStringAsSlice", `expects " or n, but found `+string([]byte{c})) return } +func (iter *Iterator) readStringAsSliceSlowPath() (ret []byte) { + var str []byte + var c byte + for iter.Error == nil { + c = iter.readByte() + if c == '"' { + return str + } + if c == '\\' { + c = iter.readByte() + str = iter.readEscapedChar(c, str) + } else { + str = append(str, c) + } + } + iter.ReportError("readStringAsSliceSlowPath", "unexpected end of input") + return +} + func (iter *Iterator) readU4() (ret rune) { for i := 0; i < 4; i++ { c := iter.readByte() diff --git a/misc_tests/jsoniter_object_test.go b/misc_tests/jsoniter_object_test.go index 00807bae..f8e549b2 100644 --- a/misc_tests/jsoniter_object_test.go +++ b/misc_tests/jsoniter_object_test.go @@ -3,12 +3,12 @@ package misc_tests import ( "bytes" "reflect" + "strings" "testing" + "time" - "github.com/json-iterator/go" + jsoniter "github.com/json-iterator/go" "github.com/stretchr/testify/require" - "strings" - "time" ) func Test_empty_object(t *testing.T) { @@ -83,6 +83,51 @@ func Test_write_object(t *testing.T) { should.Equal("{\n \"hello\": 1,\n \"world\": 2\n}", buf.String()) } +func Test_ReadStringAsSlice(t *testing.T) { + testCase := `{"a": "{object \"a\": \"b\"}"}` + should := require.New(t) + iter := jsoniter.ParseString(jsoniter.ConfigDefault, testCase) + field := iter.ReadObject() + should.Equal("a", field) + value := iter.ReadStringAsSlice() + should.Equal("{object \"a\": \"b\"}", string(value)) + field = iter.ReadObject() + should.Equal("", field) + + iter = jsoniter.ParseString(jsoniter.ConfigDefault, testCase) + should.True(iter.ReadObjectCB(func(iter *jsoniter.Iterator, field string) bool { + should.Equal("a", field) + iter.Skip() + return true + })) + +} + +func Test_ReadStringAsSlice_escapes(t *testing.T) { + should := require.New(t) + + testCases := []struct { + name string + input string + expected string + }{ + {"simple_string", `"hello"`, "hello"}, + {"escaped_quotes", `"say \"hello\""`, `say "hello"`}, + {"escaped_backslash", `"path\\to\\file"`, `path\to\file`}, + {"escaped_newline", `"line1\nline2"`, "line1\nline2"}, + {"escaped_tab", `"col1\tcol2"`, "col1\tcol2"}, + {"complex_escape", `"\"quotes\" and \\backslashes\\"`, `"quotes" and \backslashes\`}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + iter := jsoniter.ParseString(jsoniter.ConfigDefault, tc.input) + value := iter.ReadStringAsSlice() + should.Equal(tc.expected, string(value), "Failed for input: %s", tc.input) + }) + } +} + func Test_reader_and_load_more(t *testing.T) { should := require.New(t) type TestObject struct {