diff --git a/ast/encode.go b/ast/encode.go index 147709d..e07b9cd 100644 --- a/ast/encode.go +++ b/ast/encode.go @@ -17,13 +17,17 @@ package ast import ( + `reflect` `sync` + `unsafe` `github.com/bytedance/sonic/encoder` + `github.com/bytedance/sonic/internal/native` + `github.com/bytedance/sonic/internal/rt` ) const ( - _MaxBuffer = 4 * 1024 // 4KB buffer size + _MaxBuffer = 1024 // 1KB buffer size ) const ( @@ -113,10 +117,45 @@ func (self *Node) encodeNumber(buf *[]byte) error { return nil } +var typeByte = rt.UnpackType(reflect.TypeOf(byte(0))) + +func quote(buf *[]byte, sp unsafe.Pointer, nb int) { + b := (*rt.GoSlice)(unsafe.Pointer(buf)) + // input buffer + for nb > 0 { + // output buffer + dp := unsafe.Pointer(uintptr(b.Ptr) + uintptr(b.Len)) + dn := b.Cap - b.Len + // call native.Quote, dn is byte count it outputs + ret := native.Quote(sp, nb, dp, &dn, 0) + // update *buf length + b.Len += dn + + // no need more output + if ret >= 0 { + break + } + + // double buf size + *b = growslice(typeByte, *b, b.Cap * 2) + // ret is the complement of consumed input + ret = ^ret + // update input buffer + nb -= ret + sp = unsafe.Pointer(uintptr(sp) + uintptr(ret)) + } +} + func (self *Node) encodeString(buf *[]byte) error { - str := addr2str(self.p, self.v) *buf = append(*buf, '"') - *buf = append(*buf, str...) + nb := int(self.v) + if nb == 0 { + *buf = append(*buf, '"') + return nil + } + + quote(buf, self.p, nb) + *buf = append(*buf, '"') return nil } @@ -156,7 +195,14 @@ func (self *Node) encodeArray(buf *[]byte) error { func (self *Pair) encode(buf *[]byte) error { *buf = append(*buf, '"') - *buf = append(*buf, self.Key...) + sptr := (*rt.GoString)(unsafe.Pointer(&self.Key)) + if sptr.Len == 0 { + *buf = append(*buf, '"', ':') + return self.Value.encode(buf) + } + + quote(buf, sptr.Ptr, sptr.Len) + *buf = append(*buf, '"', ':') return self.Value.encode(buf) } diff --git a/ast/encode_test.go b/ast/encode_test.go index 39e4a7b..1f4648d 100644 --- a/ast/encode_test.go +++ b/ast/encode_test.go @@ -72,6 +72,10 @@ func TestEncodeValue(t *testing.T) { if err != nil { t.Fatal(err) } + quote, err := encoder.Encode(_TwitterJson, 0) + if err != nil { + t.Fatal(err) + } type Case struct { node Node exp string @@ -83,9 +87,12 @@ func TestEncodeValue(t *testing.T) { {NewBool(false), "false", false}, {NewNumber("0.0"), "0.0", false}, {NewString(""), `""`, false}, + {NewString("\"\""), `"\"\""`, false}, + {NewString(_TwitterJson), string(quote), false}, {NewArray([]Node{}), "[]", false}, - {NewArray([]Node{NewBool(true), NewString("true")}), `[true,"true"]`, false}, + {NewArray([]Node{NewBool(true), NewString("true"), NewString("\t")}), `[true,"true","\t"]`, false}, {NewObject([]Pair{Pair{"a", NewNull()}, Pair{"b", NewNumber("0")}}), `{"a":null,"b":0}`, false}, + {NewObject([]Pair{Pair{"\ta", NewString("\t")}, Pair{"\bb", NewString("\b")}, Pair{"\nb", NewString("\n")}, Pair{"\ra", NewString("\r")}}), `{"\ta":"\t","\bb":"\b","\nb":"\n","\ra":"\r"}`, false}, {NewObject([]Pair{}), `{}`, false}, {NewBytes([]byte("hello, world")), `"aGVsbG8sIHdvcmxk"`, false}, {NewAny(obj), string(buf), false}, @@ -145,6 +152,10 @@ func BenchmarkEncodeRaw(b *testing.B) { if e != nil { b.Fatal(root) } + _, err := root.MarshalJSON() + if err != nil { + b.Fatal(err) + } b.SetBytes(int64(len(data))) b.ResetTimer() for i:=0; i