mirror of
https://github.com/netbirdio/easyjson.git
synced 2026-05-22 18:44:42 -07:00
Merge branch 'kirillx-master'
This commit is contained in:
@@ -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 \
|
||||
|
||||
@@ -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` /
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package tests
|
||||
|
||||
//easyjson:json
|
||||
type NocopyStruct struct {
|
||||
A string `json:"a"`
|
||||
B string `json:"b,nocopy"`
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user