Merge branch 'master' into master

This commit is contained in:
GoWebProd
2020-03-30 23:46:53 +03:00
committed by GitHub
46 changed files with 6797 additions and 8701 deletions
+2
View File
@@ -2,3 +2,5 @@
*_easyjson.go
*.iml
.idea
*.swp
bin/*
+7 -4
View File
@@ -2,8 +2,11 @@ language: go
go:
- tip
- stable
matrix:
allow_failures:
- go: tip
install:
- go get github.com/ugorji/go/codec
- go get github.com/pquerna/ffjson/fflib/v1
- go get github.com/json-iterator/go
- go get github.com/golang/lint/golint
- go get golang.org/x/lint/golint
+42 -40
View File
@@ -1,56 +1,58 @@
PKG=github.com/mailru/easyjson
GOPATH:=$(PWD)/.root:$(GOPATH)
export GOPATH
all: test
.root/src/$(PKG):
mkdir -p $@
for i in $$PWD/* ; do ln -s $$i $@/`basename $$i` ; done
root: .root/src/$(PKG)
clean:
rm -rf .root
rm -rf bin
rm -rf tests/*_easyjson.go
rm -rf benchmark/*_easyjson.go
build:
go build -i -o .root/bin/easyjson $(PKG)/easyjson
go build -i -o ./bin/easyjson ./easyjson
generate: root build
.root/bin/easyjson -stubs \
.root/src/$(PKG)/tests/snake.go \
.root/src/$(PKG)/tests/data.go \
.root/src/$(PKG)/tests/omitempty.go \
.root/src/$(PKG)/tests/nothing.go \
.root/src/$(PKG)/tests/named_type.go
generate: build
bin/easyjson -stubs \
./tests/snake.go \
./tests/data.go \
./tests/omitempty.go \
./tests/nothing.go \
./tests/named_type.go \
./tests/custom_map_key_type.go \
./tests/embedded_type.go \
./tests/reference_to_pointer.go \
./tests/html.go \
./tests/unknown_fields.go \
./tests/type_declaration.go
.root/bin/easyjson -all .root/src/$(PKG)/tests/data.go
.root/bin/easyjson -all .root/src/$(PKG)/tests/nothing.go
.root/bin/easyjson -all .root/src/$(PKG)/tests/errors.go
.root/bin/easyjson -snake_case .root/src/$(PKG)/tests/snake.go
.root/bin/easyjson -omit_empty .root/src/$(PKG)/tests/omitempty.go
.root/bin/easyjson -build_tags=use_easyjson .root/src/$(PKG)/benchmark/data.go
.root/bin/easyjson .root/src/$(PKG)/tests/nested_easy.go
.root/bin/easyjson .root/src/$(PKG)/tests/named_type.go
bin/easyjson -all ./tests/data.go
bin/easyjson -all ./tests/nothing.go
bin/easyjson -all ./tests/errors.go
bin/easyjson -all ./tests/html.go
bin/easyjson -snake_case ./tests/snake.go
bin/easyjson -omit_empty ./tests/omitempty.go
bin/easyjson -build_tags=use_easyjson ./benchmark/data.go
bin/easyjson ./tests/nested_easy.go
bin/easyjson ./tests/named_type.go
bin/easyjson ./tests/custom_map_key_type.go
bin/easyjson ./tests/embedded_type.go
bin/easyjson ./tests/reference_to_pointer.go
bin/easyjson ./tests/key_marshaler_map.go
bin/easyjson -disallow_unknown_fields ./tests/disallow_unknown.go
bin/easyjson ./tests/unknown_fields.go
bin/easyjson ./tests/type_declaration.go
test: generate root
test: generate
go test \
$(PKG)/tests \
$(PKG)/jlexer \
$(PKG)/gen \
$(PKG)/buffer
go test -benchmem -tags use_easyjson -bench . $(PKG)/benchmark
golint -set_exit_status .root/src/$(PKG)/tests/*_easyjson.go
./tests \
./jlexer \
./gen \
./buffer
cd benchmark && go test -benchmem -tags use_easyjson -bench .
golint -set_exit_status ./tests/*_easyjson.go
bench-other: generate root
@go test -benchmem -bench . $(PKG)/benchmark
@go test -benchmem -tags use_ffjson -bench . $(PKG)/benchmark
@go test -benchmem -tags use_jsoniter -bench . $(PKG)/benchmark
@go test -benchmem -tags use_codec -bench . $(PKG)/benchmark
bench-other: generate
cd benchmark && make
bench-python:
benchmark/ujson.sh
.PHONY: root clean generate test build
.PHONY: clean generate test build
+10 -5
View File
@@ -55,6 +55,8 @@ Usage of easyjson:
use lowerCamelCase instead of CamelCase by default
-stubs
only generate stubs for marshaler/unmarshaler funcs
-disallow_unknown_fields
return error if some unknown field in json appeared
```
Using `-all` will generate marshalers/unmarshalers for all Go structs in the
@@ -79,7 +81,7 @@ Additional option notes:
## Generated Marshaler/Unmarshaler Funcs
For Go struct types, easyjson generates the funcs `MarshalEasyJSON` /
`UnmarshalEasyJSON` for marshaling/unmarshaling JSON. In turn, these satisify
`UnmarshalEasyJSON` for marshaling/unmarshaling JSON. In turn, these satisfy
the `easyjson.Marshaler` and `easyjson.Unmarshaler` interfaces and when used in
conjunction with `easyjson.Marshal` / `easyjson.Unmarshal` avoid unnecessary
reflection / type assertions during marshaling/unmarshaling to/from JSON for Go
@@ -102,17 +104,17 @@ utility funcs that are available.
## Controlling easyjson Marshaling and Unmarshaling Behavior
Go types can provide their own `MarshalEasyJSON` and `UnmarshalEasyJSON` funcs
that satisify the `easyjson.Marshaler` / `easyjson.Unmarshaler` interfaces.
that satisfy the `easyjson.Marshaler` / `easyjson.Unmarshaler` interfaces.
These will be used by `easyjson.Marshal` and `easyjson.Unmarshal` when defined
for a Go type.
Go types can also satisify the `easyjson.Optional` interface, which allows the
Go types can also satisfy the `easyjson.Optional` interface, which allows the
type to define its own `omitempty` logic.
## Type Wrappers
easyjson provides additional type wrappers defined in the `easyjson/opt`
package. These wrap the standard Go primitives and in turn satisify the
package. These wrap the standard Go primitives and in turn satisfy the
easyjson interfaces.
The `easyjson/opt` type wrappers are useful when needing to distinguish between
@@ -173,6 +175,9 @@ for more information.
typically for many uses/protocols the final, marshaled length of the JSON
needs to be known prior to sending the data. Currently this is not possible
with easyjson's architecture.
* easyjson parser and codegen based on reflection, so it won't work on `package main`
files, because they cant be imported by parser.
## Benchmarks
@@ -236,7 +241,7 @@ since the memory is not freed between marshaling operations.
### easyjson vs 'ujson' python module
[ujson](https://github.com/esnme/ultrajson) is using C code for parsing, so it
is interesting to see how plain golang compares to that. It is imporant to note
is interesting to see how plain golang compares to that. It is important to note
that the resulting object for python is slower to access, since the library
parses JSON object into dictionaries.
+5
View File
@@ -0,0 +1,5 @@
all:
go test -benchmem -bench .
go test -benchmem -tags use_ffjson -bench .
go test -benchmem -tags use_jsoniter -bench .
go test -benchmem -tags use_codec -bench .
+3499 -6391
View File
File diff suppressed because it is too large Load Diff
+2071 -2001
View File
File diff suppressed because it is too large Load Diff
+14
View File
@@ -0,0 +1,14 @@
module github.com/mailru/easyjson/benchmark
go 1.12
require (
github.com/json-iterator/go v1.1.7
github.com/mailru/easyjson v0.0.0
github.com/pquerna/ffjson v0.0.0-20190813045741-dac163c6c0a9
github.com/ugorji/go/codec v1.1.7
github.com/ugorji/go/codec/codecgen v1.1.7
golang.org/x/tools v0.0.0-20190829051458-42f498d34c4d // indirect
)
replace github.com/mailru/easyjson => ../
+31
View File
@@ -0,0 +1,31 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/ffjson v0.0.0-20190813045741-dac163c6c0a9 h1:kyf9snWXHvQc+yxE9imhdI8YAm4oKeZISlaAR+x73zs=
github.com/pquerna/ffjson v0.0.0-20190813045741-dac163c6c0a9/go.mod h1:YARuvh7BUWHNhzDq2OM5tzR2RiCcN2D7sapiKyCel/M=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/ugorji/go/codec/codecgen v1.1.7 h1:6BU4y9NIgvVMNetSGxkH9lOOa2UB5b8sCHC6+8m5lVc=
github.com/ugorji/go/codec/codecgen v1.1.7/go.mod h1:FMgcDIjFRtjQPUVM3GN592ic8epl2qsvfNPhezMgP8Y=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190829051458-42f498d34c4d h1:yqT69RdmShXXRtsT9jS6Iy0FFLWGLCe3IqGE0vsP0m4=
golang.org/x/tools v0.0.0-20190829051458-42f498d34c4d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+9
View File
@@ -0,0 +1,9 @@
//+build tools
// Package tools tracks dependencies on binaries not otherwise referenced in the codebase.
// https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module
package tools
import (
_ "github.com/ugorji/go/codec/codecgen"
)
+27 -18
View File
@@ -7,6 +7,7 @@ package bootstrap
import (
"fmt"
"go/format"
"io/ioutil"
"os"
"os/exec"
@@ -22,10 +23,11 @@ type Generator struct {
PkgPath, PkgName string
Types []string
NoStdMarshalers bool
SnakeCase bool
LowerCamelCase bool
OmitEmpty bool
NoStdMarshalers bool
SnakeCase bool
LowerCamelCase bool
OmitEmpty bool
DisallowUnknownFields bool
OutName string
BuildTags string
@@ -37,7 +39,7 @@ type Generator struct {
SimpleBytes bool
}
// writeStub outputs an initial stubs for marshalers/unmarshalers so that the package
// writeStub outputs an initial stub for marshalers/unmarshalers so that the package
// using marshalers/unmarshales compiles correctly for boostrapping code.
func (g *Generator) writeStub() error {
f, err := os.Create(g.OutName)
@@ -122,9 +124,12 @@ func (g *Generator) writeMain() (path string, err error) {
if g.NoStdMarshalers {
fmt.Fprintln(f, " g.NoStdMarshalers()")
}
if g.SimpleBytes {
fmt.Fprintln(f, " g.SimpleBytes()")
if g.DisallowUnknownFields {
fmt.Fprintln(f, " g.DisallowUnknownFields()")
}
if g.SimpleBytes {
fmt.Fprintln(f, " g.SimpleBytes()")
}
sort.Strings(g.Types)
for _, v := range g.Types {
@@ -170,24 +175,28 @@ func (g *Generator) Run() error {
defer os.Remove(f.Name()) // will not remove after rename
}
cmd := exec.Command("go", "run", "-tags", g.BuildTags, path)
cmd := exec.Command("go", "run", "-tags", g.BuildTags, filepath.Base(path))
cmd.Stdout = f
cmd.Stderr = os.Stderr
cmd.Dir = filepath.Dir(path)
if err = cmd.Run(); err != nil {
return err
}
f.Close()
if !g.NoFormat {
cmd = exec.Command("gofmt", "-w", f.Name())
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
if err = cmd.Run(); err != nil {
return err
}
// move unformatted file to out path
if g.NoFormat {
return os.Rename(f.Name(), g.OutName)
}
return os.Rename(f.Name(), g.OutName)
// format file and write to out path
in, err := ioutil.ReadFile(f.Name())
if err != nil {
return err
}
out, err := format.Source(in)
if err != nil {
return err
}
return ioutil.WriteFile(g.OutName, out, 0644)
}
+40 -32
View File
@@ -4,6 +4,7 @@ package buffer
import (
"io"
"net"
"sync"
)
@@ -52,14 +53,12 @@ func putBuf(buf []byte) {
// getBuf gets a chunk from reuse pool or creates a new one if reuse failed.
func getBuf(size int) []byte {
if size < config.PooledSize {
return make([]byte, 0, size)
}
if c := buffers[size]; c != nil {
v := c.Get()
if v != nil {
return v.([]byte)
if size >= config.PooledSize {
if c := buffers[size]; c != nil {
v := c.Get()
if v != nil {
return v.([]byte)
}
}
}
return make([]byte, 0, size)
@@ -78,9 +77,12 @@ type Buffer struct {
// EnsureSpace makes sure that the current chunk contains at least s free bytes,
// possibly creating a new chunk.
func (b *Buffer) EnsureSpace(s int) {
if cap(b.Buf)-len(b.Buf) >= s {
return
if cap(b.Buf)-len(b.Buf) < s {
b.ensureSpaceSlow(s)
}
}
func (b *Buffer) ensureSpaceSlow(s int) {
l := len(b.Buf)
if l > 0 {
if cap(b.toPool) != cap(b.Buf) {
@@ -105,18 +107,22 @@ func (b *Buffer) EnsureSpace(s int) {
// AppendByte appends a single byte to buffer.
func (b *Buffer) AppendByte(data byte) {
if cap(b.Buf) == len(b.Buf) { // EnsureSpace won't be inlined.
b.EnsureSpace(1)
}
b.EnsureSpace(1)
b.Buf = append(b.Buf, data)
}
// AppendBytes appends a byte slice to buffer.
func (b *Buffer) AppendBytes(data []byte) {
if len(data) <= cap(b.Buf)-len(b.Buf) {
b.Buf = append(b.Buf, data...) // fast path
} else {
b.appendBytesSlow(data)
}
}
func (b *Buffer) appendBytesSlow(data []byte) {
for len(data) > 0 {
if cap(b.Buf) == len(b.Buf) { // EnsureSpace won't be inlined.
b.EnsureSpace(1)
}
b.EnsureSpace(1)
sz := cap(b.Buf) - len(b.Buf)
if sz > len(data) {
@@ -128,12 +134,18 @@ func (b *Buffer) AppendBytes(data []byte) {
}
}
// AppendBytes appends a string to buffer.
// AppendString appends a string to buffer.
func (b *Buffer) AppendString(data string) {
if len(data) <= cap(b.Buf)-len(b.Buf) {
b.Buf = append(b.Buf, data...) // fast path
} else {
b.appendStringSlow(data)
}
}
func (b *Buffer) appendStringSlow(data string) {
for len(data) > 0 {
if cap(b.Buf) == len(b.Buf) { // EnsureSpace won't be inlined.
b.EnsureSpace(1)
}
b.EnsureSpace(1)
sz := cap(b.Buf) - len(b.Buf)
if sz > len(data) {
@@ -156,18 +168,14 @@ func (b *Buffer) Size() int {
// DumpTo outputs the contents of a buffer to a writer and resets the buffer.
func (b *Buffer) DumpTo(w io.Writer) (written int, err error) {
var n int
for _, buf := range b.bufs {
if err == nil {
n, err = w.Write(buf)
written += n
}
putBuf(buf)
bufs := net.Buffers(b.bufs)
if len(b.Buf) > 0 {
bufs = append(bufs, b.Buf)
}
n, err := bufs.WriteTo(w)
if err == nil {
n, err = w.Write(b.Buf)
written += n
for _, buf := range b.bufs {
putBuf(buf)
}
putBuf(b.toPool)
@@ -175,7 +183,7 @@ func (b *Buffer) DumpTo(w io.Writer) (written int, err error) {
b.Buf = nil
b.toPool = nil
return
return int(n), err
}
// BuildBytes creates a single byte slice with all the contents of the buffer. Data is
@@ -192,7 +200,7 @@ func (b *Buffer) BuildBytes(reuse ...[]byte) []byte {
var ret []byte
size := b.Size()
// If we got a buffer as argument and it is big enought, reuse it.
// If we got a buffer as argument and it is big enough, reuse it.
if len(reuse) == 1 && cap(reuse[0]) >= size {
ret = reuse[0][:0]
} else {
+1 -1
View File
@@ -42,7 +42,7 @@ func TestAppendString(t *testing.T) {
s := "test"
for i := 0; i < 1000; i++ {
b.AppendBytes([]byte(s))
b.AppendString(s)
want = append(want, s...)
}
+15 -13
View File
@@ -28,6 +28,7 @@ var stubs = flag.Bool("stubs", false, "only generate stubs for marshaler/unmarsh
var noformat = flag.Bool("noformat", false, "do not run 'gofmt -w' on output file")
var specifiedName = flag.String("output_filename", "", "specify the filename of the output")
var processPkg = flag.Bool("pkg", false, "process the whole package instead of just the given file")
var disallowUnknownFields = flag.Bool("disallow_unknown_fields", false, "return error if any unknown field in json appeared")
func generate(fname string) (err error) {
fInfo, err := os.Stat(fname)
@@ -61,19 +62,20 @@ func generate(fname string) (err error) {
}
g := bootstrap.Generator{
BuildTags: trimmedBuildTags,
PkgPath: p.PkgPath,
PkgName: p.PkgName,
Types: p.StructNames,
SnakeCase: *snakeCase,
LowerCamelCase: *lowerCamelCase,
NoStdMarshalers: *noStdMarshalers,
OmitEmpty: *omitEmpty,
LeaveTemps: *leaveTemps,
OutName: outName,
StubsOnly: *stubs,
NoFormat: *noformat,
SimpleBytes: *simpleBytes,
BuildTags: trimmedBuildTags,
PkgPath: p.PkgPath,
PkgName: p.PkgName,
Types: p.StructNames,
SnakeCase: *snakeCase,
LowerCamelCase: *lowerCamelCase,
NoStdMarshalers: *noStdMarshalers,
DisallowUnknownFields: *disallowUnknownFields,
OmitEmpty: *omitEmpty,
LeaveTemps: *leaveTemps,
OutName: outName,
StubsOnly: *stubs,
NoFormat: *noformat,
SimpleBytes: *simpleBytes,
}
if err := g.Run(); err != nil {
+87 -23
View File
@@ -48,6 +48,8 @@ var primitiveStringDecoders = map[reflect.Kind]string{
reflect.Uint32: "in.Uint32Str()",
reflect.Uint64: "in.Uint64Str()",
reflect.Uintptr: "in.UintptrStr()",
reflect.Float32: "in.Float32Str()",
reflect.Float64: "in.Float64Str()",
}
var customDecoders = map[string]string{
@@ -84,6 +86,24 @@ func (g *Generator) genTypeDecoder(t reflect.Type, out string, tags fieldTags, i
return err
}
// returns true of the type t implements one of the custom unmarshaler interfaces
func hasCustomUnmarshaler(t reflect.Type) bool {
t = reflect.PtrTo(t)
return t.Implements(reflect.TypeOf((*easyjson.Unmarshaler)(nil)).Elem()) ||
t.Implements(reflect.TypeOf((*json.Unmarshaler)(nil)).Elem()) ||
t.Implements(reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem())
}
func hasUnknownsUnmarshaler(t reflect.Type) bool {
t = reflect.PtrTo(t)
return t.Implements(reflect.TypeOf((*easyjson.UnknownsUnmarshaler)(nil)).Elem())
}
func hasUnknownsMarshaler(t reflect.Type) bool {
t = reflect.PtrTo(t)
return t.Implements(reflect.TypeOf((*easyjson.UnknownsMarshaler)(nil)).Elem())
}
// genTypeDecoderNoCheck generates decoding code for the type t.
func (g *Generator) genTypeDecoderNoCheck(t reflect.Type, out string, tags fieldTags, indent int) error {
ws := strings.Repeat(" ", indent)
@@ -104,7 +124,7 @@ func (g *Generator) genTypeDecoderNoCheck(t reflect.Type, out string, tags field
tmpVar := g.uniqueVarName()
elem := t.Elem()
if elem.Kind() == reflect.Uint8 {
if elem.Kind() == reflect.Uint8 && elem.Name() == "uint8" {
fmt.Fprintln(g.out, ws+"if in.IsNull() {")
fmt.Fprintln(g.out, ws+" in.Skip()")
fmt.Fprintln(g.out, ws+" "+out+" = nil")
@@ -119,9 +139,9 @@ func (g *Generator) genTypeDecoderNoCheck(t reflect.Type, out string, tags field
} else {
capacity := minSliceBytes / elem.Size()
if capacity == 0 {
capacity = 1
capacity := 1
if elem.Size() > 0 {
capacity = minSliceBytes / int(elem.Size())
}
fmt.Fprintln(g.out, ws+"if in.IsNull() {")
@@ -156,7 +176,7 @@ func (g *Generator) genTypeDecoderNoCheck(t reflect.Type, out string, tags field
iterVar := g.uniqueVarName()
elem := t.Elem()
if elem.Kind() == reflect.Uint8 {
if elem.Kind() == reflect.Uint8 && elem.Name() == "uint8" {
fmt.Fprintln(g.out, ws+"if in.IsNull() {")
fmt.Fprintln(g.out, ws+" in.Skip()")
fmt.Fprintln(g.out, ws+"} else {")
@@ -175,7 +195,7 @@ func (g *Generator) genTypeDecoderNoCheck(t reflect.Type, out string, tags field
fmt.Fprintln(g.out, ws+" for !in.IsDelim(']') {")
fmt.Fprintln(g.out, ws+" if "+iterVar+" < "+fmt.Sprint(length)+" {")
if err := g.genTypeDecoder(elem, out+"["+iterVar+"]", tags, indent+3); err != nil {
if err := g.genTypeDecoder(elem, "("+out+")["+iterVar+"]", tags, indent+3); err != nil {
return err
}
@@ -193,7 +213,12 @@ func (g *Generator) genTypeDecoderNoCheck(t reflect.Type, out string, tags field
dec := g.getDecoderName(t)
g.addType(t)
fmt.Fprintln(g.out, ws+dec+"(in, &"+out+")")
if len(out) > 0 && out[0] == '*' {
// NOTE: In order to remove an extra reference to a pointer
fmt.Fprintln(g.out, ws+dec+"(in, "+out[1:]+")")
} else {
fmt.Fprintln(g.out, ws+dec+"(in, &"+out+")")
}
case reflect.Ptr:
fmt.Fprintln(g.out, ws+"if in.IsNull() {")
@@ -213,24 +238,43 @@ func (g *Generator) genTypeDecoderNoCheck(t reflect.Type, out string, tags field
case reflect.Map:
key := t.Key()
keyDec, ok := primitiveStringDecoders[key.Kind()]
if !ok {
return fmt.Errorf("map type %v not supported: only string and integer keys are allowed", key)
}
if !ok && !hasCustomUnmarshaler(key) {
return fmt.Errorf("map type %v not supported: only string and integer keys and types implementing json.Unmarshaler are allowed", key)
} // else assume the caller knows what they are doing and that the custom unmarshaler performs the translation from string or integer keys to the key type
elem := t.Elem()
tmpVar := g.uniqueVarName()
keepEmpty := tags.required || tags.noOmitEmpty || (!g.omitEmpty && !tags.omitEmpty)
fmt.Fprintln(g.out, ws+"if in.IsNull() {")
fmt.Fprintln(g.out, ws+" in.Skip()")
fmt.Fprintln(g.out, ws+"} else {")
fmt.Fprintln(g.out, ws+" in.Delim('{')")
fmt.Fprintln(g.out, ws+" if !in.IsDelim('}') {")
if !keepEmpty {
fmt.Fprintln(g.out, ws+" if !in.IsDelim('}') {")
}
fmt.Fprintln(g.out, ws+" "+out+" = make("+g.getType(t)+")")
fmt.Fprintln(g.out, ws+" } else {")
fmt.Fprintln(g.out, ws+" "+out+" = nil")
fmt.Fprintln(g.out, ws+" }")
if !keepEmpty {
fmt.Fprintln(g.out, ws+" } else {")
fmt.Fprintln(g.out, ws+" "+out+" = nil")
fmt.Fprintln(g.out, ws+" }")
}
fmt.Fprintln(g.out, ws+" for !in.IsDelim('}') {")
fmt.Fprintln(g.out, ws+" key := "+g.getType(key)+"("+keyDec+")")
// NOTE: extra check for TextUnmarshaler. It overrides default methods.
if reflect.PtrTo(key).Implements(reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()) {
fmt.Fprintln(g.out, ws+" var key "+g.getType(key))
fmt.Fprintln(g.out, ws+"if data := in.UnsafeBytes(); in.Ok() {")
fmt.Fprintln(g.out, ws+" in.AddError(key.UnmarshalText(data) )")
fmt.Fprintln(g.out, ws+"}")
} else if keyDec != "" {
fmt.Fprintln(g.out, ws+" key := "+g.getType(key)+"("+keyDec+")")
} else {
fmt.Fprintln(g.out, ws+" var key "+g.getType(key))
if err := g.genTypeDecoder(key, "key", tags, indent+2); err != nil {
return err
}
}
fmt.Fprintln(g.out, ws+" in.WantColon()")
fmt.Fprintln(g.out, ws+" var "+tmpVar+" "+g.getType(elem))
@@ -328,9 +372,11 @@ func getStructFields(t reflect.Type) ([]reflect.StructField, error) {
}
var efields []reflect.StructField
var fields []reflect.StructField
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
if !f.Anonymous {
tags := parseFieldTags(f)
if !f.Anonymous || tags.name != "" {
continue
}
@@ -339,17 +385,25 @@ func getStructFields(t reflect.Type) ([]reflect.StructField, error) {
t1 = t1.Elem()
}
fs, err := getStructFields(t1)
if err != nil {
return nil, fmt.Errorf("error processing embedded field: %v", err)
if t1.Kind() == reflect.Struct {
fs, err := getStructFields(t1)
if err != nil {
return nil, fmt.Errorf("error processing embedded field: %v", err)
}
efields = mergeStructFields(efields, fs)
} else if (t1.Kind() >= reflect.Bool && t1.Kind() < reflect.Complex128) || t1.Kind() == reflect.String {
if strings.Contains(f.Name, ".") || unicode.IsUpper([]rune(f.Name)[0]) {
fields = append(fields, f)
}
}
efields = mergeStructFields(efields, fs)
}
var fields []reflect.StructField
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
if f.Anonymous {
tags := parseFieldTags(f)
if f.Anonymous && tags.name == "" {
continue
}
@@ -448,7 +502,17 @@ func (g *Generator) genStructDecoder(t reflect.Type) error {
}
fmt.Fprintln(g.out, " default:")
fmt.Fprintln(g.out, " in.SkipRecursive()")
if g.disallowUnknownFields {
fmt.Fprintln(g.out, ` in.AddError(&jlexer.LexerError{
Offset: in.GetPos(),
Reason: "unknown field",
Data: key,
})`)
} else if hasUnknownsUnmarshaler(t) {
fmt.Fprintln(g.out, " out.UnmarshalUnknown(in, key)")
} else {
fmt.Fprintln(g.out, " in.SkipRecursive()")
}
fmt.Fprintln(g.out, " }")
fmt.Fprintln(g.out, " in.WantComma()")
fmt.Fprintln(g.out, " }")
+74 -22
View File
@@ -45,6 +45,8 @@ var primitiveStringEncoders = map[reflect.Kind]string{
reflect.Uint32: "out.Uint32Str(uint32(%v))",
reflect.Uint64: "out.Uint64Str(uint64(%v))",
reflect.Uintptr: "out.UintptrStr(uintptr(%v))",
reflect.Float32: "out.Float32Str(float32(%v))",
reflect.Float64: "out.Float64Str(float64(%v))",
}
// fieldTags contains parsed version of json struct field tags.
@@ -108,6 +110,14 @@ func (g *Generator) genTypeEncoder(t reflect.Type, in string, tags fieldTags, in
return err
}
// returns true of the type t implements one of the custom marshaler interfaces
func hasCustomMarshaler(t reflect.Type) bool {
t = reflect.PtrTo(t)
return t.Implements(reflect.TypeOf((*easyjson.Marshaler)(nil)).Elem()) ||
t.Implements(reflect.TypeOf((*json.Marshaler)(nil)).Elem()) ||
t.Implements(reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem())
}
// genTypeEncoderNoCheck generates code that encodes in of type t into the writer.
func (g *Generator) genTypeEncoderNoCheck(t reflect.Type, in string, tags fieldTags, indent int, assumeNonEmpty bool) error {
ws := strings.Repeat(" ", indent)
@@ -116,7 +126,9 @@ func (g *Generator) genTypeEncoderNoCheck(t reflect.Type, in string, tags fieldT
if enc := primitiveStringEncoders[t.Kind()]; enc != "" && tags.asString {
fmt.Fprintf(g.out, ws+enc+"\n", in)
return nil
} else if enc := primitiveEncoders[t.Kind()]; enc != "" {
}
if enc := primitiveEncoders[t.Kind()]; enc != "" {
fmt.Fprintf(g.out, ws+enc+"\n", in)
return nil
}
@@ -127,13 +139,12 @@ func (g *Generator) genTypeEncoderNoCheck(t reflect.Type, in string, tags fieldT
iVar := g.uniqueVarName()
vVar := g.uniqueVarName()
if t.Elem().Kind() == reflect.Uint8 {
if t.Elem().Kind() == reflect.Uint8 && elem.Name() == "uint8" {
if g.simpleBytes {
fmt.Fprintln(g.out, ws+"out.String(string("+in+"))")
} else {
fmt.Fprintln(g.out, ws+"out.Base64Bytes("+in+")")
}
} else {
if !assumeNonEmpty {
fmt.Fprintln(g.out, ws+"if "+in+" == nil && (out.Flags & jwriter.NilSliceAsEmpty) == 0 {")
@@ -161,7 +172,7 @@ func (g *Generator) genTypeEncoderNoCheck(t reflect.Type, in string, tags fieldT
elem := t.Elem()
iVar := g.uniqueVarName()
if t.Elem().Kind() == reflect.Uint8 {
if t.Elem().Kind() == reflect.Uint8 && elem.Name() == "uint8" {
if g.simpleBytes {
fmt.Fprintln(g.out, ws+"out.String(string("+in+"[:]))")
} else {
@@ -174,7 +185,7 @@ func (g *Generator) genTypeEncoderNoCheck(t reflect.Type, in string, tags fieldT
fmt.Fprintln(g.out, ws+" out.RawByte(',')")
fmt.Fprintln(g.out, ws+" }")
if err := g.genTypeEncoder(elem, in+"["+iVar+"]", tags, indent+1, false); err != nil {
if err := g.genTypeEncoder(elem, "("+in+")["+iVar+"]", tags, indent+1, false); err != nil {
return err
}
@@ -206,9 +217,9 @@ func (g *Generator) genTypeEncoderNoCheck(t reflect.Type, in string, tags fieldT
case reflect.Map:
key := t.Key()
keyEnc, ok := primitiveStringEncoders[key.Kind()]
if !ok {
return fmt.Errorf("map key type %v not supported: only string and integer keys are allowed", key)
}
if !ok && !hasCustomMarshaler(key) {
return fmt.Errorf("map key type %v not supported: only string and integer keys and types implementing Marshaler interfaces are allowed", key)
} // else assume the caller knows what they are doing and that the custom marshaler performs the translation from the key type to a string or integer
tmpVar := g.uniqueVarName()
if !assumeNonEmpty {
@@ -222,7 +233,18 @@ func (g *Generator) genTypeEncoderNoCheck(t reflect.Type, in string, tags fieldT
fmt.Fprintln(g.out, ws+" "+tmpVar+"First := true")
fmt.Fprintln(g.out, ws+" for "+tmpVar+"Name, "+tmpVar+"Value := range "+in+" {")
fmt.Fprintln(g.out, ws+" if "+tmpVar+"First { "+tmpVar+"First = false } else { out.RawByte(',') }")
fmt.Fprintln(g.out, ws+" "+fmt.Sprintf(keyEnc, tmpVar+"Name"))
// NOTE: extra check for TextMarshaler. It overrides default methods.
if reflect.PtrTo(key).Implements(reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem()) {
fmt.Fprintln(g.out, ws+" "+fmt.Sprintf("out.RawText(("+tmpVar+"Name).MarshalText()"+")"))
} else if keyEnc != "" {
fmt.Fprintln(g.out, ws+" "+fmt.Sprintf(keyEnc, tmpVar+"Name"))
} else {
if err := g.genTypeEncoder(key, tmpVar+"Name", tags, indent+2, false); err != nil {
return err
}
}
fmt.Fprintln(g.out, ws+" out.RawByte(':')")
if err := g.genTypeEncoder(t.Elem(), tmpVar+"Value", tags, indent+2, false); err != nil {
@@ -278,32 +300,50 @@ func (g *Generator) notEmptyCheck(t reflect.Type, v string) string {
}
}
func (g *Generator) genStructFieldEncoder(t reflect.Type, f reflect.StructField) error {
func (g *Generator) genStructFieldEncoder(t reflect.Type, f reflect.StructField, first, firstCondition bool) (bool, error) {
jsonName := g.fieldNamer.GetJSONFieldName(t, f)
tags := parseFieldTags(f)
if tags.omit {
return nil
return firstCondition, nil
}
toggleFirstCondition := firstCondition
noOmitEmpty := (!tags.omitEmpty && !g.omitEmpty) || tags.noOmitEmpty
if noOmitEmpty {
fmt.Fprintln(g.out, " {")
toggleFirstCondition = false
} else {
fmt.Fprintln(g.out, " if", g.notEmptyCheck(f.Type, "in."+f.Name), "{")
// can be any in runtime, so toggleFirstCondition stay as is
}
if firstCondition {
fmt.Fprintf(g.out, " const prefix string = %q\n", ","+strconv.Quote(jsonName)+":")
if first {
if !noOmitEmpty {
fmt.Fprintln(g.out, " first = false")
}
fmt.Fprintln(g.out, " out.RawString(prefix[1:])")
} else {
fmt.Fprintln(g.out, " if first {")
fmt.Fprintln(g.out, " first = false")
fmt.Fprintln(g.out, " out.RawString(prefix[1:])")
fmt.Fprintln(g.out, " } else {")
fmt.Fprintln(g.out, " out.RawString(prefix)")
fmt.Fprintln(g.out, " }")
}
} else {
fmt.Fprintf(g.out, " const prefix string = %q\n", ","+strconv.Quote(jsonName)+":")
fmt.Fprintln(g.out, " out.RawString(prefix)")
}
fmt.Fprintf(g.out, " const prefix string = %q\n", ","+strconv.Quote(jsonName)+":")
fmt.Fprintln(g.out, " if first {")
fmt.Fprintln(g.out, " first = false")
fmt.Fprintln(g.out, " out.RawString(prefix[1:])")
fmt.Fprintln(g.out, " } else {")
fmt.Fprintln(g.out, " out.RawString(prefix)")
fmt.Fprintln(g.out, " }")
if err := g.genTypeEncoder(f.Type, "in."+f.Name, tags, 2, !noOmitEmpty); err != nil {
return err
return toggleFirstCondition, err
}
fmt.Fprintln(g.out, " }")
return nil
return toggleFirstCondition, nil
}
func (g *Generator) genEncoder(t reflect.Type) error {
@@ -351,12 +391,24 @@ func (g *Generator) genStructEncoder(t reflect.Type) error {
if err != nil {
return fmt.Errorf("cannot generate encoder for %v: %v", t, err)
}
for _, f := range fs {
if err := g.genStructFieldEncoder(t, f); err != nil {
firstCondition := true
for i, f := range fs {
firstCondition, err = g.genStructFieldEncoder(t, f, i == 0, firstCondition)
if err != nil {
return err
}
}
if hasUnknownsMarshaler(t) {
if !firstCondition {
fmt.Fprintln(g.out, " in.MarshalUnknowns(out, false)")
} else {
fmt.Fprintln(g.out, " in.MarshalUnknowns(out, first)")
}
}
fmt.Fprintln(g.out, " out.RawByte('}')")
fmt.Fprintln(g.out, "}")
+26 -14
View File
@@ -33,10 +33,11 @@ type Generator struct {
varCounter int
noStdMarshalers bool
omitEmpty bool
simpleBytes bool
fieldNamer FieldNamer
noStdMarshalers bool
omitEmpty bool
disallowUnknownFields bool
fieldNamer FieldNamer
simpleBytes bool
// package path to local alias map for tracking imports
imports map[string]string
@@ -111,6 +112,11 @@ func (g *Generator) NoStdMarshalers() {
g.noStdMarshalers = true
}
// DisallowUnknownFields instructs not to skip unknown fields in json and return error.
func (g *Generator) DisallowUnknownFields() {
g.disallowUnknownFields = true
}
// OmitEmpty triggers `json=",omitempty"` behaviour by default.
func (g *Generator) OmitEmpty() {
g.omitEmpty = true
@@ -156,8 +162,9 @@ func (g *Generator) printHeader() {
fmt.Println("package ", g.pkgName)
fmt.Println()
byAlias := map[string]string{}
var aliases []string
byAlias := make(map[string]string, len(g.imports))
aliases := make([]string, 0, len(g.imports))
for path, alias := range g.imports {
aliases = append(aliases, alias)
byAlias[alias] = path
@@ -290,7 +297,11 @@ func (g *Generator) getType(t reflect.Type) string {
lines := make([]string, 0, nf)
for i := 0; i < nf; i++ {
f := t.Field(i)
line := f.Name + " " + g.getType(f.Type)
var line string
if !f.Anonymous {
line = f.Name + " "
} // else the field is anonymous (an embedded type)
line += g.getType(f.Type)
t := f.Tag
if t != "" {
line += " " + escapeTag(t)
@@ -384,9 +395,9 @@ func (DefaultFieldNamer) GetJSONFieldName(t reflect.Type, f reflect.StructField)
jsonName := strings.Split(f.Tag.Get("json"), ",")[0]
if jsonName != "" {
return jsonName
} else {
return f.Name
}
return f.Name
}
// LowerCamelCaseFieldNamer
@@ -423,9 +434,10 @@ func lowerFirst(s string) string {
for i := range s {
ch := s[i]
if isUpper(ch) {
if i == 0 {
switch {
case i == 0:
str += string(ch + 32)
} else if !foundLower { // Currently just a stream of capitals, eg JSONRESTS[erver]
case !foundLower: // Currently just a stream of capitals, eg JSONRESTS[erver]
if strlen > (i+1) && isLower(s[i+1]) {
// Next char is lower, keep this a capital
str += string(ch)
@@ -433,7 +445,7 @@ func lowerFirst(s string) string {
// Either at end of string or next char is capital
str += string(ch + 32)
}
} else {
default:
str += string(ch)
}
} else {
@@ -449,9 +461,9 @@ func (LowerCamelCaseFieldNamer) GetJSONFieldName(t reflect.Type, f reflect.Struc
jsonName := strings.Split(f.Tag.Get("json"), ",")[0]
if jsonName != "" {
return jsonName
} else {
return lowerFirst(f.Name)
}
return lowerFirst(f.Name)
}
// SnakeCaseFieldNamer implements CamelCase to snake_case conversion for fields names.
+3
View File
@@ -0,0 +1,3 @@
module github.com/mailru/easyjson
go 1.12
+10
View File
@@ -26,6 +26,16 @@ type Optional interface {
IsDefined() bool
}
// UnknownsUnmarshaler provides a method to unmarshal unknown struct fileds and save them as you want
type UnknownsUnmarshaler interface {
UnmarshalUnknown(in *jlexer.Lexer, key string)
}
// UnknownsMarshaler provides a method to write additional struct fields
type UnknownsMarshaler interface {
MarshalUnknowns(w *jwriter.Writer, first bool)
}
// Marshal returns data as a single byte slice. Method is suboptimal as the data is likely to be copied
// from a chain of smaller chunks.
func Marshal(v Marshaler) ([]byte, error) {
+53 -12
View File
@@ -240,7 +240,7 @@ func (r *Lexer) fetchNumber() {
// findStringLen tries to scan into the string literal for ending quote char to determine required size.
// The size will be exact if no escapes are present and may be inexact if there are escaped chars.
func findStringLen(data []byte) (hasEscapes bool, length int) {
func findStringLen(data []byte) (isValid, hasEscapes bool, length int) {
delta := 0
for i := 0; i < len(data); i++ {
@@ -252,11 +252,11 @@ func findStringLen(data []byte) (hasEscapes bool, length int) {
delta++
}
case '"':
return (delta > 0), (i - delta)
return true, (delta > 0), (i - delta)
}
}
return false, len(data)
return false, false, len(data)
}
// getu4 decodes \uXXXX from the beginning of s, returning the hex value,
@@ -342,7 +342,12 @@ func (r *Lexer) fetchString() {
r.pos++
data := r.Data[r.pos:]
hasEscapes, length := findStringLen(data)
isValid, hasEscapes, length := findStringLen(data)
if !isValid {
r.pos += length
r.errParse("unterminated string literal")
return
}
if !hasEscapes {
r.token.byteValue = data[:length]
r.pos += length + 1
@@ -516,11 +521,12 @@ func (r *Lexer) SkipRecursive() {
r.scanToken()
var start, end byte
if r.token.delimValue == '{' {
switch r.token.delimValue {
case '{':
start, end = '{', '}'
} else if r.token.delimValue == '[' {
case '[':
start, end = '[', ']'
} else {
default:
r.consume()
return
}
@@ -649,7 +655,7 @@ func (r *Lexer) Bytes() []byte {
return nil
}
ret := make([]byte, base64.StdEncoding.DecodedLen(len(r.token.byteValue)))
len, err := base64.StdEncoding.Decode(ret, r.token.byteValue)
n, err := base64.StdEncoding.Decode(ret, r.token.byteValue)
if err != nil {
r.fatalError = &LexerError{
Reason: err.Error(),
@@ -658,7 +664,7 @@ func (r *Lexer) Bytes() []byte {
}
r.consume()
return ret[:len]
return ret[:n]
}
// Bool reads a true or false boolean keyword.
@@ -997,6 +1003,22 @@ func (r *Lexer) Float32() float32 {
return float32(n)
}
func (r *Lexer) Float32Str() float32 {
s, b := r.unsafeString()
if !r.Ok() {
return 0
}
n, err := strconv.ParseFloat(s, 32)
if err != nil {
r.addNonfatalError(&LexerError{
Offset: r.start,
Reason: err.Error(),
Data: string(b),
})
}
return float32(n)
}
func (r *Lexer) Float64() float64 {
s := r.number()
if !r.Ok() {
@@ -1014,6 +1036,22 @@ func (r *Lexer) Float64() float64 {
return n
}
func (r *Lexer) Float64Str() float64 {
s, b := r.unsafeString()
if !r.Ok() {
return 0
}
n, err := strconv.ParseFloat(s, 64)
if err != nil {
r.addNonfatalError(&LexerError{
Offset: r.start,
Reason: err.Error(),
Data: string(b),
})
}
return n
}
func (r *Lexer) Error() error {
return r.fatalError
}
@@ -1056,7 +1094,7 @@ func (r *Lexer) JsonNumber() json.Number {
}
if !r.Ok() {
r.errInvalidToken("json.Number")
return json.Number("0")
return json.Number("")
}
switch r.token.kind {
@@ -1064,9 +1102,12 @@ func (r *Lexer) JsonNumber() json.Number {
return json.Number(r.String())
case tokenNumber:
return json.Number(r.Raw())
case tokenNull:
r.Null()
return json.Number("")
default:
r.errSyntax()
return json.Number("0")
return json.Number("")
}
}
@@ -1111,7 +1152,7 @@ func (r *Lexer) Interface() interface{} {
} else if r.token.delimValue == '[' {
r.consume()
var ret []interface{}
ret := []interface{}{}
for !r.IsDelim(']') {
ret = append(ret, r.Interface())
r.WantComma()

Some files were not shown because too many files have changed in this diff Show More