Merge branch 'kirillx-master'

This commit is contained in:
Alexandr Mayorskiy
2020-04-12 21:35:56 +03:00
6 changed files with 96 additions and 1 deletions
+3 -1
View File
@@ -23,7 +23,8 @@ generate: build
./tests/type_declaration.go \
./tests/members_escaped.go \
./tests/members_unescaped.go \
./tests/intern.go
./tests/intern.go \
./tests/nocopy.go \
bin/easyjson -all ./tests/data.go
bin/easyjson -all ./tests/nothing.go
@@ -44,6 +45,7 @@ generate: build
bin/easyjson ./tests/members_escaped.go
bin/easyjson -disable_members_unescape ./tests/members_unescaped.go
bin/easyjson ./tests/intern.go
bin/easyjson ./tests/nocopy.go
test: generate
go test \
+12
View File
@@ -80,6 +80,18 @@ Additional option notes:
* `-build_tags` will add the specified build tags to generated Go sources.
## Structure json tag options
Besides standart json tag options like 'omitempty' the following are supported:
* 'nocopy' - disables allocation and copying of string values, making them
refer to original json buffer memory. This works great for short lived
objects which are not hold in memory after decoding and immediate usage.
Note if string requires unescaping it will be processed as normally.
* 'intern' - string "interning" (deduplication) to save memory when the very
same string dictionary values are often met all over the structure.
See below for more details.
## Generated Marshaler/Unmarshaler Funcs
For Go struct types, easyjson generates the funcs `MarshalEasyJSON` /
+7
View File
@@ -3,6 +3,7 @@ package gen
import (
"encoding"
"encoding/json"
"errors"
"fmt"
"reflect"
"strings"
@@ -121,6 +122,9 @@ func (g *Generator) genTypeDecoderNoCheck(t reflect.Type, out string, tags field
if tags.intern && t.Kind() == reflect.String {
dec = "in.StringIntern()"
}
if tags.noCopy && t.Kind() == reflect.String {
dec = "in.UnsafeString()"
}
fmt.Fprintln(g.out, ws+out+" = "+g.getType(t)+"("+dec+")")
return nil
}
@@ -335,6 +339,9 @@ func (g *Generator) genStructFieldDecoder(t reflect.Type, f reflect.StructField)
if tags.omit {
return nil
}
if tags.intern && tags.noCopy {
return errors.New("Mutually exclusive tags are specified: 'intern' and 'nocopy'")
}
fmt.Fprintf(g.out, " case %q:\n", jsonName)
if err := g.genTypeDecoder(f.Type, "out."+f.Name, tags, 3); err != nil {
+3
View File
@@ -59,6 +59,7 @@ type fieldTags struct {
asString bool
required bool
intern bool
noCopy bool
}
// parseFieldTags parses the json field tag into a structure.
@@ -81,6 +82,8 @@ func parseFieldTags(f reflect.StructField) fieldTags {
ret.required = true
case s == "intern":
ret.intern = true
case s == "nocopy":
ret.noCopy = true
}
}
+7
View File
@@ -0,0 +1,7 @@
package tests
//easyjson:json
type NocopyStruct struct {
A string `json:"a"`
B string `json:"b,nocopy"`
}
+64
View File
@@ -0,0 +1,64 @@
package tests
import (
"reflect"
"testing"
"unsafe"
"github.com/mailru/easyjson"
)
// verifies if string pointer belongs to the given buffer or outside of it
func strBelongsTo(s string, buf []byte) bool {
sPtr := (*reflect.StringHeader)(unsafe.Pointer(&s)).Data
bufPtr := (*reflect.SliceHeader)(unsafe.Pointer(&buf)).Data
if bufPtr <= sPtr && sPtr < bufPtr+uintptr(len(buf)) {
return true
}
return false
}
func TestNocopy(t *testing.T) {
data := []byte(`{"a": "valueA", "b": "valueB"}`)
exp := NocopyStruct{
A: "valueA",
B: "valueB",
}
res := NocopyStruct{}
easyjson.Unmarshal(data, &res)
if !reflect.DeepEqual(exp, res) {
t.Errorf("TestNocopy(): got=%+v, exp=%+v", res, exp)
}
if strBelongsTo(res.A, data) {
t.Error("TestNocopy(): field A was not copied and refers to buffer")
}
if !strBelongsTo(res.B, data) {
t.Error("TestNocopy(): field B was copied rather than refer to bufferr")
}
data = []byte(`{"b": "valueNoCopy"}`)
res = NocopyStruct{}
allocsPerRun := testing.AllocsPerRun(1000, func() {
easyjson.Unmarshal(data, &res)
if res.B != "valueNoCopy" {
t.Fatalf("wrong value: %q", res.B)
}
})
if allocsPerRun != 1 {
t.Fatalf("noCopy field unmarshal: expected 1 allocs, got %f", allocsPerRun)
}
data = []byte(`{"a": "valueNoCopy"}`)
allocsPerRun = testing.AllocsPerRun(1000, func() {
easyjson.Unmarshal(data, &res)
if res.A != "valueNoCopy" {
t.Fatalf("wrong value: %q", res.A)
}
})
if allocsPerRun != 2 {
t.Fatalf("copy field unmarshal: expected 2 allocs, got %f", allocsPerRun)
}
}