mirror of
https://github.com/netbirdio/easyjson.git
synced 2026-05-22 18:44:42 -07:00
Merge branch 'master' into master
This commit is contained in:
@@ -2,3 +2,5 @@
|
||||
*_easyjson.go
|
||||
*.iml
|
||||
.idea
|
||||
*.swp
|
||||
bin/*
|
||||
|
||||
+7
-4
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
+2071
-2001
File diff suppressed because it is too large
Load Diff
@@ -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 => ../
|
||||
@@ -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=
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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.
|
||||
|
||||
+10
@@ -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
@@ -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
Reference in New Issue
Block a user