Skip to content

Use ReadStringAsSlice instead of ReadString #697

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions any.go
Original file line number Diff line number Diff line change
@@ -3,11 +3,12 @@ package jsoniter
import (
"errors"
"fmt"
"github.com/modern-go/reflect2"
"io"
"reflect"
"strconv"
"unsafe"

"github.com/modern-go/reflect2"
)

// Any generic object representation.
@@ -155,7 +156,7 @@ func (iter *Iterator) readAny() Any {
switch c {
case '"':
iter.unreadByte()
return &stringAny{baseAny{}, iter.ReadString()}
return &stringAny{baseAny{}, string(iter.ReadStringAsSlice())}
case 'n':
iter.skipThreeBytes('u', 'l', 'l') // null
return &nilAny{}
19 changes: 8 additions & 11 deletions extra/binary_as_string_codec.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package extra

import (
"github.com/json-iterator/go"
"github.com/modern-go/reflect2"
"unicode/utf8"
"unsafe"

"github.com/modern-go/reflect2"

"github.com/json-iterator/go"
)

// safeSet holds the value true if the ASCII character with the given array
@@ -142,19 +144,14 @@ func (codec *binaryAsStringCodec) Decode(ptr unsafe.Pointer, iter *jsoniter.Iter
b := rawBytes[i]
if b == '\\' {
b2 := rawBytes[i+1]
if b2 != '\\' {
iter.ReportError("decode binary as string", `\\x is only supported escape`)
if b2 != 'x' {
iter.ReportError("decode binary as string", `\x is only supported escape`)
return
}
b3 := rawBytes[i+2]
if b3 != 'x' {
iter.ReportError("decode binary as string", `\\x is only supported escape`)
return
}
b4 := rawBytes[i+3]
b5 := rawBytes[i+4]
i += 4
b = readHex(iter, b4, b5)
i += 3
b = readHex(iter, b3, b4)
}
bytes = append(bytes, b)
}
19 changes: 9 additions & 10 deletions iter_object.go
Original file line number Diff line number Diff line change
@@ -59,7 +59,7 @@ func (iter *Iterator) readFieldHash() int64 {
b := iter.buf[i]
if b == '\\' {
iter.head = i
for _, b := range iter.readStringSlowPath() {
for _, b := range iter.readStringSlowPathAsSlice() {
if 'A' <= b && b <= 'Z' && !iter.cfg.caseSensitive {
b += 'a' - 'A'
}
@@ -110,31 +110,30 @@ func calcHash(str string, caseSensitive bool) int64 {
// ReadObjectCB read object with callback, the key is ascii only and field name not copied
func (iter *Iterator) ReadObjectCB(callback func(*Iterator, string) bool) bool {
c := iter.nextToken()
var field string
if c == '{' {
if !iter.incrementDepth() {
return false
}
c = iter.nextToken()
if c == '"' {
iter.unreadByte()
field = iter.ReadString()
field := iter.ReadStringAsSlice()
c = iter.nextToken()
if c != ':' {
iter.ReportError("ReadObject", "expect : after object field, but found "+string([]byte{c}))
}
if !callback(iter, field) {
if !callback(iter, string(field)) {
iter.decrementDepth()
return false
}
c = iter.nextToken()
for c == ',' {
field = iter.ReadString()
field = iter.ReadStringAsSlice()
c = iter.nextToken()
if c != ':' {
iter.ReportError("ReadObject", "expect : after object field, but found "+string([]byte{c}))
}
if !callback(iter, field) {
if !callback(iter, string(field)) {
iter.decrementDepth()
return false
}
@@ -172,25 +171,25 @@ func (iter *Iterator) ReadMapCB(callback func(*Iterator, string) bool) bool {
c = iter.nextToken()
if c == '"' {
iter.unreadByte()
field := iter.ReadString()
field := iter.ReadStringAsSlice()
if iter.nextToken() != ':' {
iter.ReportError("ReadMapCB", "expect : after object field, but found "+string([]byte{c}))
iter.decrementDepth()
return false
}
if !callback(iter, field) {
if !callback(iter, string(field)) {
iter.decrementDepth()
return false
}
c = iter.nextToken()
for c == ',' {
field = iter.ReadString()
field = iter.ReadStringAsSlice()
if iter.nextToken() != ':' {
iter.ReportError("ReadMapCB", "expect : after object field, but found "+string([]byte{c}))
iter.decrementDepth()
return false
}
if !callback(iter, field) {
if !callback(iter, string(field)) {
iter.decrementDepth()
return false
}
5 changes: 3 additions & 2 deletions iter_skip_strict.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//+build !jsoniter_sloppy
//go:build !jsoniter_sloppy
// +build !jsoniter_sloppy

package jsoniter

@@ -61,7 +62,7 @@ func (iter *Iterator) trySkipNumber() bool {
func (iter *Iterator) skipString() {
if !iter.trySkipString() {
iter.unreadByte()
iter.ReadString()
iter.ReadStringAsSlice()
}
}

56 changes: 15 additions & 41 deletions iter_str.go
Original file line number Diff line number Diff line change
@@ -7,38 +7,44 @@ import (

// ReadString read string from iterator
func (iter *Iterator) ReadString() (ret string) {
return string(iter.ReadStringAsSlice())
}

// ReadStringAsSlice read string from iterator without copying into string form.
// The []byte can not be kept, as it will change after next iterator call.
func (iter *Iterator) ReadStringAsSlice() (ret []byte) {
c := iter.nextToken()
if c == '"' {
for i := iter.head; i < iter.tail; i++ {
c := iter.buf[i]
if c == '"' {
ret = string(iter.buf[iter.head:i])
head := iter.head
iter.head = i + 1
return ret
return iter.buf[head:i]
} else if c == '\\' {
break
} else if c < ' ' {
iter.ReportError("ReadString",
iter.ReportError("ReadStringAsSlice",
fmt.Sprintf(`invalid control character found: %d`, c))
return
}
}
return iter.readStringSlowPath()
return iter.readStringSlowPathAsSlice()
} else if c == 'n' {
iter.skipThreeBytes('u', 'l', 'l')
return ""
return
}
iter.ReportError("ReadString", `expects " or n, but found `+string([]byte{c}))
iter.ReportError("ReadStringAsSlice", `expects " or n, but found `+string([]byte{c}))
return
}

func (iter *Iterator) readStringSlowPath() (ret string) {
func (iter *Iterator) readStringSlowPathAsSlice() (ret []byte) {
var str []byte
var c byte
for iter.Error == nil {
c = iter.readByte()
if c == '"' {
return string(str)
return str
}
if c == '\\' {
c = iter.readByte()
@@ -47,7 +53,7 @@ func (iter *Iterator) readStringSlowPath() (ret string) {
str = append(str, c)
}
}
iter.ReportError("readStringSlowPath", "unexpected end of input")
iter.ReportError("readStringSlowPathAsSlice", "unexpected end of input")
return
}

@@ -111,38 +117,6 @@ func (iter *Iterator) readEscapedChar(c byte, str []byte) []byte {
return str
}

// ReadStringAsSlice read string from iterator without copying into string form.
// The []byte can not be kept, as it will change after next iterator call.
func (iter *Iterator) ReadStringAsSlice() (ret []byte) {
c := iter.nextToken()
if c == '"' {
for i := iter.head; i < iter.tail; i++ {
// require ascii string and no escape
// for: field name, base64, number
if iter.buf[i] == '"' {
// fast path: reuse the underlying buffer
ret = iter.buf[iter.head:i]
iter.head = i + 1
return ret
}
}
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
}
iter.ReportError("ReadStringAsSlice", `expects " or n, but found `+string([]byte{c}))
return
}

func (iter *Iterator) readU4() (ret rune) {
for i := 0; i < 4; i++ {
c := iter.readByte()
7 changes: 4 additions & 3 deletions misc_tests/jsoniter_object_test.go
Original file line number Diff line number Diff line change
@@ -3,12 +3,13 @@ package misc_tests
import (
"bytes"
"reflect"
"strings"
"testing"
"time"

"github.com/json-iterator/go"
"github.com/stretchr/testify/require"
"strings"
"time"

"github.com/json-iterator/go"
)

func Test_empty_object(t *testing.T) {
11 changes: 7 additions & 4 deletions reflect_json_number.go
Original file line number Diff line number Diff line change
@@ -2,9 +2,10 @@ package jsoniter

import (
"encoding/json"
"github.com/modern-go/reflect2"
"strconv"
"unsafe"

"github.com/modern-go/reflect2"
)

type Number string
@@ -61,7 +62,7 @@ type jsonNumberCodec struct {
func (codec *jsonNumberCodec) Decode(ptr unsafe.Pointer, iter *Iterator) {
switch iter.WhatIsNext() {
case StringValue:
*((*json.Number)(ptr)) = json.Number(iter.ReadString())
*((*json.Number)(ptr)) = json.Number(iter.ReadStringAsSlice())
case NilValue:
iter.skipFourBytes('n', 'u', 'l', 'l')
*((*json.Number)(ptr)) = ""
@@ -89,12 +90,14 @@ type jsoniterNumberCodec struct {
func (codec *jsoniterNumberCodec) Decode(ptr unsafe.Pointer, iter *Iterator) {
switch iter.WhatIsNext() {
case StringValue:
*((*Number)(ptr)) = Number(iter.ReadString())
num := iter.ReadStringAsSlice()
*((*Number)(ptr)) = Number(string(num))
case NilValue:
iter.skipFourBytes('n', 'u', 'l', 'l')
*((*Number)(ptr)) = ""
default:
*((*Number)(ptr)) = Number([]byte(iter.readNumberAsString()))
num := iter.ReadStringAsSlice()
*((*Number)(ptr)) = Number(string(num))
}
}

2 changes: 1 addition & 1 deletion reflect_map.go
Original file line number Diff line number Diff line change
@@ -305,7 +305,7 @@ func (encoder *sortKeysMapEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
}
encodedKey := subStream.Buffer()[subStreamIndex:]
subIter.ResetBytes(encodedKey)
decodedKey := subIter.ReadString()
decodedKey := string(subIter.ReadStringAsSlice())
if stream.indention > 0 {
subStream.writeTwoBytes(byte(':'), byte(' '))
} else {
4 changes: 2 additions & 2 deletions reflect_marshaler.go
Original file line number Diff line number Diff line change
@@ -217,8 +217,8 @@ func (decoder *textUnmarshalerDecoder) Decode(ptr unsafe.Pointer, iter *Iterator
obj = valType.UnsafeIndirect(ptr)
}
unmarshaler := (obj).(encoding.TextUnmarshaler)
str := iter.ReadString()
err := unmarshaler.UnmarshalText([]byte(str))
str := iter.ReadStringAsSlice()
err := unmarshaler.UnmarshalText(str)
if err != nil {
iter.ReportError("textUnmarshalerDecoder", err.Error())
}
7 changes: 4 additions & 3 deletions reflect_native.go
Original file line number Diff line number Diff line change
@@ -206,7 +206,8 @@ type stringCodec struct {
}

func (codec *stringCodec) Decode(ptr unsafe.Pointer, iter *Iterator) {
*((*string)(ptr)) = iter.ReadString()
data := iter.ReadStringAsSlice()
*((*string)(ptr)) = string(data)
}

func (codec *stringCodec) Encode(ptr unsafe.Pointer, stream *Stream) {
@@ -417,8 +418,8 @@ func (codec *base64Codec) Decode(ptr unsafe.Pointer, iter *Iterator) {
}
switch iter.WhatIsNext() {
case StringValue:
src := iter.ReadString()
dst, err := base64.StdEncoding.DecodeString(src)
src := iter.ReadStringAsSlice()
dst, err := base64.StdEncoding.DecodeString(string(src))
if err != nil {
iter.ReportError("decode base64", err.Error())
} else {
22 changes: 6 additions & 16 deletions reflect_struct_decoder.go
Original file line number Diff line number Diff line change
@@ -517,21 +517,10 @@ func (decoder *generalStructDecoder) Decode(ptr unsafe.Pointer, iter *Iterator)
}

func (decoder *generalStructDecoder) decodeOneField(ptr unsafe.Pointer, iter *Iterator) {
var field string
var fieldDecoder *structFieldDecoder
if iter.cfg.objectFieldMustBeSimpleString {
fieldBytes := iter.ReadStringAsSlice()
field = *(*string)(unsafe.Pointer(&fieldBytes))
fieldDecoder = decoder.fields[field]
if fieldDecoder == nil && !iter.cfg.caseSensitive {
fieldDecoder = decoder.fields[strings.ToLower(field)]
}
} else {
field = iter.ReadString()
fieldDecoder = decoder.fields[field]
if fieldDecoder == nil && !iter.cfg.caseSensitive {
fieldDecoder = decoder.fields[strings.ToLower(field)]
}
field := string(iter.ReadStringAsSlice())
fieldDecoder := decoder.fields[field]
if fieldDecoder == nil && !iter.cfg.caseSensitive {
fieldDecoder = decoder.fields[strings.ToLower(field)]
}
if fieldDecoder == nil {
if decoder.disallowUnknownFields {
@@ -1067,7 +1056,8 @@ func (decoder *stringModeStringDecoder) Decode(ptr unsafe.Pointer, iter *Iterato
str := *((*string)(ptr))
tempIter := decoder.cfg.BorrowIterator([]byte(str))
defer decoder.cfg.ReturnIterator(tempIter)
*((*string)(ptr)) = tempIter.ReadString()
data := tempIter.ReadStringAsSlice()
*((*string)(ptr)) = string(data)
}

type stringModeNumberDecoder struct {