TUN-6293: Update yaml v3 to latest hotfix
This addresses https://security.snyk.io/vuln/SNYK-GOLANG-GOPKGINYAMLV3-2841557 by updating yaml v3 to latest version. It also stops using yaml v2 directly (we were using both v2 and v3 mixed). We still rely on yaml v2 indirectly, via urfave cli, though. Note that the security vulnerability does not affect v2.
This commit is contained in:
parent
919227fc91
commit
5e6f606f4e
|
@ -20,7 +20,7 @@ import (
|
|||
"github.com/urfave/cli/v2"
|
||||
"github.com/urfave/cli/v2/altsrc"
|
||||
"golang.org/x/net/idna"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/cloudflare/cloudflared/cfapi"
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func TestConfigFileSettings(t *testing.T) {
|
||||
|
@ -111,7 +111,7 @@ counters:
|
|||
|
||||
}
|
||||
|
||||
var rawConfig = []byte(`
|
||||
var rawJsonConfig = []byte(`
|
||||
{
|
||||
"connectTimeout": 10,
|
||||
"tlsTimeout": 30,
|
||||
|
@ -148,15 +148,14 @@ func TestMarshalUnmarshalOriginRequest(t *testing.T) {
|
|||
name string
|
||||
marshalFunc func(in interface{}) (out []byte, err error)
|
||||
unMarshalFunc func(in []byte, out interface{}) (err error)
|
||||
baseUnit time.Duration
|
||||
}{
|
||||
{"json", json.Marshal, json.Unmarshal, time.Second},
|
||||
{"yaml", yaml.Marshal, yaml.Unmarshal, time.Nanosecond},
|
||||
{"json", json.Marshal, json.Unmarshal},
|
||||
{"yaml", yaml.Marshal, yaml.Unmarshal},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
assertConfig(t, tc.marshalFunc, tc.unMarshalFunc, tc.baseUnit)
|
||||
assertConfig(t, tc.marshalFunc, tc.unMarshalFunc)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -165,18 +164,17 @@ func assertConfig(
|
|||
t *testing.T,
|
||||
marshalFunc func(in interface{}) (out []byte, err error),
|
||||
unMarshalFunc func(in []byte, out interface{}) (err error),
|
||||
baseUnit time.Duration,
|
||||
) {
|
||||
var config OriginRequestConfig
|
||||
var config2 OriginRequestConfig
|
||||
|
||||
assert.NoError(t, unMarshalFunc(rawConfig, &config))
|
||||
assert.NoError(t, json.Unmarshal(rawJsonConfig, &config))
|
||||
|
||||
assert.Equal(t, baseUnit*10, config.ConnectTimeout.Duration)
|
||||
assert.Equal(t, baseUnit*30, config.TLSTimeout.Duration)
|
||||
assert.Equal(t, baseUnit*30, config.TCPKeepAlive.Duration)
|
||||
assert.Equal(t, time.Second*10, config.ConnectTimeout.Duration)
|
||||
assert.Equal(t, time.Second*30, config.TLSTimeout.Duration)
|
||||
assert.Equal(t, time.Second*30, config.TCPKeepAlive.Duration)
|
||||
assert.Equal(t, true, *config.NoHappyEyeballs)
|
||||
assert.Equal(t, baseUnit*60, config.KeepAliveTimeout.Duration)
|
||||
assert.Equal(t, time.Second*60, config.KeepAliveTimeout.Duration)
|
||||
assert.Equal(t, 10, *config.KeepAliveConnections)
|
||||
assert.Equal(t, "app.tunnel.com", *config.HTTPHostHeader)
|
||||
assert.Equal(t, "app.tunnel.com", *config.OriginServerName)
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/cloudflare/cloudflared/watcher"
|
||||
)
|
||||
|
|
5
go.mod
5
go.mod
|
@ -44,8 +44,7 @@ require (
|
|||
gopkg.in/coreos/go-oidc.v2 v2.2.1
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||
gopkg.in/square/go-jose.v2 v2.6.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
zombiezen.com/go/capnproto2 v2.18.0+incompatible
|
||||
)
|
||||
|
||||
|
@ -64,7 +63,6 @@ require (
|
|||
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
|
||||
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 // indirect
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
|
||||
github.com/francoispqt/gojay v1.2.13 // indirect
|
||||
github.com/gdamore/encoding v1.0.0 // indirect
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
|
@ -102,6 +100,7 @@ require (
|
|||
google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb // indirect
|
||||
google.golang.org/grpc v1.45.0 // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/urfave/cli/v2 => github.com/ipostelnik/cli/v2 v2.3.1-0.20210324024421-b6ea8234fe3d
|
||||
|
|
4
go.sum
4
go.sum
|
@ -184,7 +184,6 @@ github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI
|
|||
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
||||
github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
||||
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
||||
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
|
||||
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
|
||||
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
|
@ -1110,8 +1109,9 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
|||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/urfave/cli/v2"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/cloudflare/cloudflared/config"
|
||||
"github.com/cloudflare/cloudflared/ipaccess"
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/urfave/cli/v2"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/cloudflare/cloudflared/config"
|
||||
"github.com/cloudflare/cloudflared/ipaccess"
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
vendor
|
||||
*.out
|
||||
*.log
|
||||
*.test
|
||||
.vscode
|
|
@ -1,15 +0,0 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
- "1.10.x"
|
||||
- "1.11.x"
|
||||
- "1.12.x"
|
||||
|
||||
script:
|
||||
- go get github.com/golang/dep/cmd/dep github.com/stretchr/testify
|
||||
- dep ensure -v -vendor-only
|
||||
- go test ./gojay/codegen/test/... -race
|
||||
- go test -race -coverprofile=coverage.txt -covermode=atomic
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
|
@ -1,163 +0,0 @@
|
|||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
digest = "1:1a37f9f2ae10d161d9688fb6008ffa14e1631e5068cc3e9698008b9e8d40d575"
|
||||
name = "cloud.google.com/go"
|
||||
packages = ["compute/metadata"]
|
||||
pruneopts = ""
|
||||
revision = "457ea5c15ccf3b87db582c450e80101989da35f7"
|
||||
version = "v0.40.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:968d8903d598e3fae738325d3410f33f07ea6a2b9ee5591e9c262ee37df6845a"
|
||||
name = "github.com/go-errors/errors"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "a6af135bd4e28680facf08a3d206b454abc877a4"
|
||||
version = "v1.0.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:529d738b7976c3848cae5cf3a8036440166835e389c1f617af701eeb12a0518d"
|
||||
name = "github.com/golang/protobuf"
|
||||
packages = ["proto"]
|
||||
pruneopts = ""
|
||||
revision = "b5d812f8a3706043e23a9cd5babf2e5423744d30"
|
||||
version = "v1.3.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:cae59d7b8243c671c9f544965522ba35c0fec48ee80adb9f1400cd2f33abbbec"
|
||||
name = "github.com/mailru/easyjson"
|
||||
packages = [
|
||||
".",
|
||||
"buffer",
|
||||
"jlexer",
|
||||
"jwriter",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "1ea4449da9834f4d333f1cc461c374aea217d249"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:1d7e1867c49a6dd9856598ef7c3123604ea3daabf5b83f303ff457bcbc410b1d"
|
||||
name = "github.com/pkg/errors"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "ba968bfe8b2f7e042a574c888954fccecfa385b4"
|
||||
version = "v0.8.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:8d4bbd8ab012efc77ab6b97286f2aff262bcdeac9803bb57d75cf7d0a5e6a877"
|
||||
name = "github.com/viant/assertly"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "04f45e0aeb6f3455884877b047a97bcc95dc9493"
|
||||
version = "v0.4.8"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:5913451bc2d274673c0716efe226a137625740cd9380641f4d8300ff4f2d82a0"
|
||||
name = "github.com/viant/toolbox"
|
||||
packages = [
|
||||
".",
|
||||
"cred",
|
||||
"data",
|
||||
"storage",
|
||||
"url",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "1be8e4d172138324f40d55ea61a2aeab0c5ce864"
|
||||
version = "v0.24.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:9d150270ca2c3356f2224a0878daa1652e4d0b25b345f18b4f6e156cc4b8ec5e"
|
||||
name = "golang.org/x/crypto"
|
||||
packages = [
|
||||
"blowfish",
|
||||
"curve25519",
|
||||
"ed25519",
|
||||
"ed25519/internal/edwards25519",
|
||||
"internal/chacha20",
|
||||
"internal/subtle",
|
||||
"poly1305",
|
||||
"ssh",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "f99c8df09eb5bff426315721bfa5f16a99cad32c"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:5a56f211e7c12a65c5585c629457a2fb91d8719844ee8fab92727ea8adb5721c"
|
||||
name = "golang.org/x/net"
|
||||
packages = [
|
||||
"context",
|
||||
"context/ctxhttp",
|
||||
"websocket",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "461777fb6f67e8cb9d70cda16573678d085a74cf"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:01bdbbc604dcd5afb6f66a717f69ad45e9643c72d5bc11678d44ffa5c50f9e42"
|
||||
name = "golang.org/x/oauth2"
|
||||
packages = [
|
||||
".",
|
||||
"google",
|
||||
"internal",
|
||||
"jws",
|
||||
"jwt",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "0f29369cfe4552d0e4bcddc57cc75f4d7e672a33"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:8ddb956f67d4c176abbbc42b7514aaeaf9ea30daa24e27d2cf30ad82f9334a2c"
|
||||
name = "golang.org/x/sys"
|
||||
packages = ["cpu"]
|
||||
pruneopts = ""
|
||||
revision = "1e42afee0f762ed3d76e6dd942e4181855fd1849"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:47f391ee443f578f01168347818cb234ed819521e49e4d2c8dd2fb80d48ee41a"
|
||||
name = "google.golang.org/appengine"
|
||||
packages = [
|
||||
".",
|
||||
"internal",
|
||||
"internal/app_identity",
|
||||
"internal/base",
|
||||
"internal/datastore",
|
||||
"internal/log",
|
||||
"internal/modules",
|
||||
"internal/remote_api",
|
||||
"internal/urlfetch",
|
||||
"urlfetch",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "b2f4a3cf3c67576a2ee09e1fe62656a5086ce880"
|
||||
version = "v1.6.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:cedccf16b71e86db87a24f8d4c70b0a855872eb967cb906a66b95de56aefbd0d"
|
||||
name = "gopkg.in/yaml.v2"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "51d6538a90f86fe93ac480b35f37b2be17fef232"
|
||||
version = "v2.2.2"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
input-imports = [
|
||||
"github.com/go-errors/errors",
|
||||
"github.com/mailru/easyjson",
|
||||
"github.com/mailru/easyjson/jlexer",
|
||||
"github.com/mailru/easyjson/jwriter",
|
||||
"github.com/viant/assertly",
|
||||
"github.com/viant/toolbox",
|
||||
"github.com/viant/toolbox/url",
|
||||
"golang.org/x/net/websocket",
|
||||
]
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
|
@ -1,23 +0,0 @@
|
|||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project"
|
||||
# version = "1.0.0"
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project2"
|
||||
# branch = "dev"
|
||||
# source = "github.com/myfork/project2"
|
||||
#
|
||||
# [[override]]
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
|
||||
|
||||
ignored = ["github.com/francoispqt/benchmarks*","github.com/stretchr/testify*","github.com/stretchr/testify","github.com/json-iterator/go","github.com/buger/jsonparser"]
|
|
@ -1,21 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2016 gojay
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -1,11 +0,0 @@
|
|||
.PHONY: test
|
||||
test:
|
||||
go test -race -run=^Test -v
|
||||
|
||||
.PHONY: cover
|
||||
cover:
|
||||
go test -coverprofile=coverage.out -covermode=atomic
|
||||
|
||||
.PHONY: coverhtml
|
||||
coverhtml:
|
||||
go tool cover -html=coverage.out
|
|
@ -1,855 +0,0 @@
|
|||
[![Build Status](https://travis-ci.org/francoispqt/gojay.svg?branch=master)](https://travis-ci.org/francoispqt/gojay)
|
||||
[![codecov](https://codecov.io/gh/francoispqt/gojay/branch/master/graph/badge.svg)](https://codecov.io/gh/francoispqt/gojay)
|
||||
[![Go Report Card](https://goreportcard.com/badge/github.com/francoispqt/gojay)](https://goreportcard.com/report/github.com/francoispqt/gojay)
|
||||
[![Go doc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square
|
||||
)](https://godoc.org/github.com/francoispqt/gojay)
|
||||
![MIT License](https://img.shields.io/badge/license-mit-blue.svg?style=flat-square)
|
||||
[![Sourcegraph](https://sourcegraph.com/github.com/francoispqt/gojay/-/badge.svg)](https://sourcegraph.com/github.com/francoispqt/gojay)
|
||||
![stability-stable](https://img.shields.io/badge/stability-stable-green.svg)
|
||||
|
||||
# GoJay
|
||||
|
||||
<img src="https://github.com/francoispqt/gojay/raw/master/gojay.png" width="200px">
|
||||
|
||||
GoJay is a performant JSON encoder/decoder for Golang (currently the most performant, [see benchmarks](#benchmark-results)).
|
||||
|
||||
It has a simple API and doesn't use reflection. It relies on small interfaces to decode/encode structures and slices.
|
||||
|
||||
Gojay also comes with powerful stream decoding features and an even faster [Unsafe](#unsafe-api) API.
|
||||
|
||||
There is also a [code generation tool](https://github.com/francoispqt/gojay/tree/master/gojay) to make usage easier and faster.
|
||||
|
||||
# Why another JSON parser?
|
||||
|
||||
I looked at other fast decoder/encoder and realised it was mostly hardly readable static code generation or a lot of reflection, poor streaming features, and not so fast in the end.
|
||||
|
||||
Also, I wanted to build a decoder that could consume an io.Reader of line or comma delimited JSON, in a JIT way. To consume a flow of JSON objects from a TCP connection for example or from a standard output. Same way I wanted to build an encoder that could encode a flow of data to a io.Writer.
|
||||
|
||||
This is how GoJay aims to be a very fast, JIT stream parser with 0 reflection, low allocation with a friendly API.
|
||||
|
||||
# Get started
|
||||
|
||||
```bash
|
||||
go get github.com/francoispqt/gojay
|
||||
```
|
||||
|
||||
* [Encoder](#encoding)
|
||||
* [Decoder](#decoding)
|
||||
* [Stream API](#stream-api)
|
||||
* [Code Generation](https://github.com/francoispqt/gojay/tree/master/gojay)
|
||||
|
||||
## Decoding
|
||||
|
||||
Decoding is done through two different API similar to standard `encoding/json`:
|
||||
* [Unmarshal](#unmarshal-api)
|
||||
* [Decode](#decode-api)
|
||||
|
||||
|
||||
Example of basic stucture decoding with Unmarshal:
|
||||
```go
|
||||
import "github.com/francoispqt/gojay"
|
||||
|
||||
type user struct {
|
||||
id int
|
||||
name string
|
||||
email string
|
||||
}
|
||||
// implement gojay.UnmarshalerJSONObject
|
||||
func (u *user) UnmarshalJSONObject(dec *gojay.Decoder, key string) error {
|
||||
switch key {
|
||||
case "id":
|
||||
return dec.Int(&u.id)
|
||||
case "name":
|
||||
return dec.String(&u.name)
|
||||
case "email":
|
||||
return dec.String(&u.email)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (u *user) NKeys() int {
|
||||
return 3
|
||||
}
|
||||
|
||||
func main() {
|
||||
u := &user{}
|
||||
d := []byte(`{"id":1,"name":"gojay","email":"gojay@email.com"}`)
|
||||
err := gojay.UnmarshalJSONObject(d, u)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
with Decode:
|
||||
```go
|
||||
func main() {
|
||||
u := &user{}
|
||||
dec := gojay.NewDecoder(bytes.NewReader([]byte(`{"id":1,"name":"gojay","email":"gojay@email.com"}`)))
|
||||
err := dec.DecodeObject(d, u)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Unmarshal API
|
||||
|
||||
Unmarshal API decodes a `[]byte` to a given pointer with a single function.
|
||||
|
||||
Behind the doors, Unmarshal API borrows a `*gojay.Decoder` resets its settings and decodes the data to the given pointer and releases the `*gojay.Decoder` to the pool when it finishes, whether it encounters an error or not.
|
||||
|
||||
If it cannot find the right Decoding strategy for the type of the given pointer, it returns an `InvalidUnmarshalError`. You can test the error returned by doing `if ok := err.(InvalidUnmarshalError); ok {}`.
|
||||
|
||||
Unmarshal API comes with three functions:
|
||||
* Unmarshal
|
||||
```go
|
||||
func Unmarshal(data []byte, v interface{}) error
|
||||
```
|
||||
|
||||
* UnmarshalJSONObject
|
||||
```go
|
||||
func UnmarshalJSONObject(data []byte, v gojay.UnmarshalerJSONObject) error
|
||||
```
|
||||
|
||||
* UnmarshalJSONArray
|
||||
```go
|
||||
func UnmarshalJSONArray(data []byte, v gojay.UnmarshalerJSONArray) error
|
||||
```
|
||||
|
||||
|
||||
### Decode API
|
||||
|
||||
Decode API decodes a `[]byte` to a given pointer by creating or borrowing a `*gojay.Decoder` with an `io.Reader` and calling `Decode` methods.
|
||||
|
||||
__Getting a *gojay.Decoder or Borrowing__
|
||||
|
||||
You can either get a fresh `*gojay.Decoder` calling `dec := gojay.NewDecoder(io.Reader)` or borrow one from the pool by calling `dec := gojay.BorrowDecoder(io.Reader)`.
|
||||
|
||||
After using a decoder, you can release it by calling `dec.Release()`. Beware, if you reuse the decoder after releasing it, it will panic with an error of type `InvalidUsagePooledDecoderError`. If you want to fully benefit from the pooling, you must release your decoders after using.
|
||||
|
||||
Example getting a fresh an releasing:
|
||||
```go
|
||||
str := ""
|
||||
dec := gojay.NewDecoder(strings.NewReader(`"test"`))
|
||||
defer dec.Release()
|
||||
if err := dec.Decode(&str); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
```
|
||||
Example borrowing a decoder and releasing:
|
||||
```go
|
||||
str := ""
|
||||
dec := gojay.BorrowDecoder(strings.NewReader(`"test"`))
|
||||
defer dec.Release()
|
||||
if err := dec.Decode(&str); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
```
|
||||
|
||||
`*gojay.Decoder` has multiple methods to decode to specific types:
|
||||
* Decode
|
||||
```go
|
||||
func (dec *gojay.Decoder) Decode(v interface{}) error
|
||||
```
|
||||
* DecodeObject
|
||||
```go
|
||||
func (dec *gojay.Decoder) DecodeObject(v gojay.UnmarshalerJSONObject) error
|
||||
```
|
||||
* DecodeArray
|
||||
```go
|
||||
func (dec *gojay.Decoder) DecodeArray(v gojay.UnmarshalerJSONArray) error
|
||||
```
|
||||
* DecodeInt
|
||||
```go
|
||||
func (dec *gojay.Decoder) DecodeInt(v *int) error
|
||||
```
|
||||
* DecodeBool
|
||||
```go
|
||||
func (dec *gojay.Decoder) DecodeBool(v *bool) error
|
||||
```
|
||||
* DecodeString
|
||||
```go
|
||||
func (dec *gojay.Decoder) DecodeString(v *string) error
|
||||
```
|
||||
|
||||
All DecodeXxx methods are used to decode top level JSON values. If you are decoding keys or items of a JSON object or array, don't use the Decode methods.
|
||||
|
||||
Example:
|
||||
```go
|
||||
reader := strings.NewReader(`"John Doe"`)
|
||||
dec := NewDecoder(reader)
|
||||
|
||||
var str string
|
||||
err := dec.DecodeString(&str)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Println(str) // John Doe
|
||||
```
|
||||
|
||||
### Structs and Maps
|
||||
#### UnmarshalerJSONObject Interface
|
||||
|
||||
To unmarshal a JSON object to a structure, the structure must implement the `UnmarshalerJSONObject` interface:
|
||||
```go
|
||||
type UnmarshalerJSONObject interface {
|
||||
UnmarshalJSONObject(*gojay.Decoder, string) error
|
||||
NKeys() int
|
||||
}
|
||||
```
|
||||
`UnmarshalJSONObject` method takes two arguments, the first one is a pointer to the Decoder (*gojay.Decoder) and the second one is the string value of the current key being parsed. If the JSON data is not an object, the UnmarshalJSONObject method will never be called.
|
||||
|
||||
`NKeys` method must return the number of keys to Unmarshal in the JSON object or 0. If zero is returned, all keys will be parsed.
|
||||
|
||||
Example of implementation for a struct:
|
||||
```go
|
||||
type user struct {
|
||||
id int
|
||||
name string
|
||||
email string
|
||||
}
|
||||
// implement UnmarshalerJSONObject
|
||||
func (u *user) UnmarshalJSONObject(dec *gojay.Decoder, key string) error {
|
||||
switch key {
|
||||
case "id":
|
||||
return dec.Int(&u.id)
|
||||
case "name":
|
||||
return dec.String(&u.name)
|
||||
case "email":
|
||||
return dec.String(&u.email)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (u *user) NKeys() int {
|
||||
return 3
|
||||
}
|
||||
```
|
||||
|
||||
Example of implementation for a `map[string]string`:
|
||||
```go
|
||||
// define our custom map type implementing UnmarshalerJSONObject
|
||||
type message map[string]string
|
||||
|
||||
// Implementing Unmarshaler
|
||||
func (m message) UnmarshalJSONObject(dec *gojay.Decoder, k string) error {
|
||||
str := ""
|
||||
err := dec.String(&str)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m[k] = str
|
||||
return nil
|
||||
}
|
||||
|
||||
// we return 0, it tells the Decoder to decode all keys
|
||||
func (m message) NKeys() int {
|
||||
return 0
|
||||
}
|
||||
```
|
||||
|
||||
### Arrays, Slices and Channels
|
||||
|
||||
To unmarshal a JSON object to a slice an array or a channel, it must implement the UnmarshalerJSONArray interface:
|
||||
```go
|
||||
type UnmarshalerJSONArray interface {
|
||||
UnmarshalJSONArray(*gojay.Decoder) error
|
||||
}
|
||||
```
|
||||
UnmarshalJSONArray method takes one argument, a pointer to the Decoder (*gojay.Decoder). If the JSON data is not an array, the Unmarshal method will never be called.
|
||||
|
||||
Example of implementation with a slice:
|
||||
```go
|
||||
type testSlice []string
|
||||
// implement UnmarshalerJSONArray
|
||||
func (t *testSlice) UnmarshalJSONArray(dec *gojay.Decoder) error {
|
||||
str := ""
|
||||
if err := dec.String(&str); err != nil {
|
||||
return err
|
||||
}
|
||||
*t = append(*t, str)
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
dec := gojay.BorrowDecoder(strings.NewReader(`["Tom", "Jim"]`))
|
||||
var slice testSlice
|
||||
err := dec.DecodeArray(&slice)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println(slice) // [Tom Jim]
|
||||
dec.Release()
|
||||
}
|
||||
```
|
||||
|
||||
Example of implementation with a channel:
|
||||
```go
|
||||
type testChannel chan string
|
||||
// implement UnmarshalerJSONArray
|
||||
func (c testChannel) UnmarshalJSONArray(dec *gojay.Decoder) error {
|
||||
str := ""
|
||||
if err := dec.String(&str); err != nil {
|
||||
return err
|
||||
}
|
||||
c <- str
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
dec := gojay.BorrowDecoder(strings.NewReader(`["Tom", "Jim"]`))
|
||||
c := make(testChannel, 2)
|
||||
err := dec.DecodeArray(c)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
for i := 0; i < 2; i++ {
|
||||
fmt.Println(<-c)
|
||||
}
|
||||
close(c)
|
||||
dec.Release()
|
||||
}
|
||||
```
|
||||
|
||||
Example of implementation with an array:
|
||||
```go
|
||||
type testArray [3]string
|
||||
// implement UnmarshalerJSONArray
|
||||
func (a *testArray) UnmarshalJSONArray(dec *Decoder) error {
|
||||
var str string
|
||||
if err := dec.String(&str); err != nil {
|
||||
return err
|
||||
}
|
||||
a[dec.Index()] = str
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
dec := gojay.BorrowDecoder(strings.NewReader(`["Tom", "Jim", "Bob"]`))
|
||||
var a testArray
|
||||
err := dec.DecodeArray(&a)
|
||||
fmt.Println(a) // [Tom Jim Bob]
|
||||
dec.Release()
|
||||
}
|
||||
```
|
||||
|
||||
### Other types
|
||||
To decode other types (string, int, int32, int64, uint32, uint64, float, booleans), you don't need to implement any interface.
|
||||
|
||||
Example of encoding strings:
|
||||
```go
|
||||
func main() {
|
||||
json := []byte(`"Jay"`)
|
||||
var v string
|
||||
err := gojay.Unmarshal(json, &v)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println(v) // Jay
|
||||
}
|
||||
```
|
||||
|
||||
### Decode values methods
|
||||
When decoding a JSON object of a JSON array using `UnmarshalerJSONObject` or `UnmarshalerJSONArray` interface, the `gojay.Decoder` provides dozens of methods to Decode multiple types.
|
||||
|
||||
Non exhaustive list of methods available (to see all methods, check the godoc):
|
||||
```go
|
||||
dec.Int
|
||||
dec.Int8
|
||||
dec.Int16
|
||||
dec.Int32
|
||||
dec.Int64
|
||||
dec.Uint8
|
||||
dec.Uint16
|
||||
dec.Uint32
|
||||
dec.Uint64
|
||||
dec.String
|
||||
dec.Time
|
||||
dec.Bool
|
||||
dec.SQLNullString
|
||||
dec.SQLNullInt64
|
||||
```
|
||||
|
||||
|
||||
## Encoding
|
||||
|
||||
Encoding is done through two different API similar to standard `encoding/json`:
|
||||
* [Marshal](#marshal-api)
|
||||
* [Encode](#encode-api)
|
||||
|
||||
Example of basic structure encoding with Marshal:
|
||||
```go
|
||||
import "github.com/francoispqt/gojay"
|
||||
|
||||
type user struct {
|
||||
id int
|
||||
name string
|
||||
email string
|
||||
}
|
||||
|
||||
// implement MarshalerJSONObject
|
||||
func (u *user) MarshalJSONObject(enc *gojay.Encoder) {
|
||||
enc.IntKey("id", u.id)
|
||||
enc.StringKey("name", u.name)
|
||||
enc.StringKey("email", u.email)
|
||||
}
|
||||
func (u *user) IsNil() bool {
|
||||
return u == nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
u := &user{1, "gojay", "gojay@email.com"}
|
||||
b, err := gojay.MarshalJSONObject(u)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println(string(b)) // {"id":1,"name":"gojay","email":"gojay@email.com"}
|
||||
}
|
||||
```
|
||||
|
||||
with Encode:
|
||||
```go
|
||||
func main() {
|
||||
u := &user{1, "gojay", "gojay@email.com"}
|
||||
b := strings.Builder{}
|
||||
enc := gojay.NewEncoder(&b)
|
||||
if err := enc.Encode(u); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println(b.String()) // {"id":1,"name":"gojay","email":"gojay@email.com"}
|
||||
}
|
||||
```
|
||||
|
||||
### Marshal API
|
||||
|
||||
Marshal API encodes a value to a JSON `[]byte` with a single function.
|
||||
|
||||
Behind the doors, Marshal API borrows a `*gojay.Encoder` resets its settings and encodes the data to an internal byte buffer and releases the `*gojay.Encoder` to the pool when it finishes, whether it encounters an error or not.
|
||||
|
||||
If it cannot find the right Encoding strategy for the type of the given value, it returns an `InvalidMarshalError`. You can test the error returned by doing `if ok := err.(InvalidMarshalError); ok {}`.
|
||||
|
||||
Marshal API comes with three functions:
|
||||
* Marshal
|
||||
```go
|
||||
func Marshal(v interface{}) ([]byte, error)
|
||||
```
|
||||
|
||||
* MarshalJSONObject
|
||||
```go
|
||||
func MarshalJSONObject(v gojay.MarshalerJSONObject) ([]byte, error)
|
||||
```
|
||||
|
||||
* MarshalJSONArray
|
||||
```go
|
||||
func MarshalJSONArray(v gojay.MarshalerJSONArray) ([]byte, error)
|
||||
```
|
||||
|
||||
### Encode API
|
||||
|
||||
Encode API decodes a value to JSON by creating or borrowing a `*gojay.Encoder` sending it to an `io.Writer` and calling `Encode` methods.
|
||||
|
||||
__Getting a *gojay.Encoder or Borrowing__
|
||||
|
||||
You can either get a fresh `*gojay.Encoder` calling `enc := gojay.NewEncoder(io.Writer)` or borrow one from the pool by calling `enc := gojay.BorrowEncoder(io.Writer)`.
|
||||
|
||||
After using an encoder, you can release it by calling `enc.Release()`. Beware, if you reuse the encoder after releasing it, it will panic with an error of type `InvalidUsagePooledEncoderError`. If you want to fully benefit from the pooling, you must release your encoders after using.
|
||||
|
||||
Example getting a fresh encoder an releasing:
|
||||
```go
|
||||
str := "test"
|
||||
b := strings.Builder{}
|
||||
enc := gojay.NewEncoder(&b)
|
||||
defer enc.Release()
|
||||
if err := enc.Encode(str); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
```
|
||||
Example borrowing an encoder and releasing:
|
||||
```go
|
||||
str := "test"
|
||||
b := strings.Builder{}
|
||||
enc := gojay.BorrowEncoder(b)
|
||||
defer enc.Release()
|
||||
if err := enc.Encode(str); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
```
|
||||
|
||||
`*gojay.Encoder` has multiple methods to encoder specific types to JSON:
|
||||
* Encode
|
||||
```go
|
||||
func (enc *gojay.Encoder) Encode(v interface{}) error
|
||||
```
|
||||
* EncodeObject
|
||||
```go
|
||||
func (enc *gojay.Encoder) EncodeObject(v gojay.MarshalerJSONObject) error
|
||||
```
|
||||
* EncodeArray
|
||||
```go
|
||||
func (enc *gojay.Encoder) EncodeArray(v gojay.MarshalerJSONArray) error
|
||||
```
|
||||
* EncodeInt
|
||||
```go
|
||||
func (enc *gojay.Encoder) EncodeInt(n int) error
|
||||
```
|
||||
* EncodeInt64
|
||||
```go
|
||||
func (enc *gojay.Encoder) EncodeInt64(n int64) error
|
||||
```
|
||||
* EncodeFloat
|
||||
```go
|
||||
func (enc *gojay.Encoder) EncodeFloat(n float64) error
|
||||
```
|
||||
* EncodeBool
|
||||
```go
|
||||
func (enc *gojay.Encoder) EncodeBool(v bool) error
|
||||
```
|
||||
* EncodeString
|
||||
```go
|
||||
func (enc *gojay.Encoder) EncodeString(s string) error
|
||||
```
|
||||
|
||||
### Structs and Maps
|
||||
|
||||
To encode a structure, the structure must implement the MarshalerJSONObject interface:
|
||||
```go
|
||||
type MarshalerJSONObject interface {
|
||||
MarshalJSONObject(enc *gojay.Encoder)
|
||||
IsNil() bool
|
||||
}
|
||||
```
|
||||
`MarshalJSONObject` method takes one argument, a pointer to the Encoder (*gojay.Encoder). The method must add all the keys in the JSON Object by calling Decoder's methods.
|
||||
|
||||
IsNil method returns a boolean indicating if the interface underlying value is nil or not. It is used to safely ensure that the underlying value is not nil without using Reflection.
|
||||
|
||||
Example of implementation for a struct:
|
||||
```go
|
||||
type user struct {
|
||||
id int
|
||||
name string
|
||||
email string
|
||||
}
|
||||
|
||||
// implement MarshalerJSONObject
|
||||
func (u *user) MarshalJSONObject(enc *gojay.Encoder) {
|
||||
enc.IntKey("id", u.id)
|
||||
enc.StringKey("name", u.name)
|
||||
enc.StringKey("email", u.email)
|
||||
}
|
||||
func (u *user) IsNil() bool {
|
||||
return u == nil
|
||||
}
|
||||
```
|
||||
|
||||
Example of implementation for a `map[string]string`:
|
||||
```go
|
||||
// define our custom map type implementing MarshalerJSONObject
|
||||
type message map[string]string
|
||||
|
||||
// Implementing Marshaler
|
||||
func (m message) MarshalJSONObject(enc *gojay.Encoder) {
|
||||
for k, v := range m {
|
||||
enc.StringKey(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
func (m message) IsNil() bool {
|
||||
return m == nil
|
||||
}
|
||||
```
|
||||
|
||||
### Arrays and Slices
|
||||
To encode an array or a slice, the slice/array must implement the MarshalerJSONArray interface:
|
||||
```go
|
||||
type MarshalerJSONArray interface {
|
||||
MarshalJSONArray(enc *gojay.Encoder)
|
||||
IsNil() bool
|
||||
}
|
||||
```
|
||||
`MarshalJSONArray` method takes one argument, a pointer to the Encoder (*gojay.Encoder). The method must add all element in the JSON Array by calling Decoder's methods.
|
||||
|
||||
`IsNil` method returns a boolean indicating if the interface underlying value is nil(empty) or not. It is used to safely ensure that the underlying value is not nil without using Reflection and also to in `OmitEmpty` feature.
|
||||
|
||||
Example of implementation:
|
||||
```go
|
||||
type users []*user
|
||||
// implement MarshalerJSONArray
|
||||
func (u *users) MarshalJSONArray(enc *gojay.Encoder) {
|
||||
for _, e := range u {
|
||||
enc.Object(e)
|
||||
}
|
||||
}
|
||||
func (u *users) IsNil() bool {
|
||||
return len(u) == 0
|
||||
}
|
||||
```
|
||||
|
||||
### Other types
|
||||
To encode other types (string, int, float, booleans), you don't need to implement any interface.
|
||||
|
||||
Example of encoding strings:
|
||||
```go
|
||||
func main() {
|
||||
name := "Jay"
|
||||
b, err := gojay.Marshal(name)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println(string(b)) // "Jay"
|
||||
}
|
||||
```
|
||||
|
||||
# Stream API
|
||||
|
||||
### Stream Decoding
|
||||
GoJay ships with a powerful stream decoder.
|
||||
|
||||
It allows to read continuously from an io.Reader stream and do JIT decoding writing unmarshalled JSON to a channel to allow async consuming.
|
||||
|
||||
When using the Stream API, the Decoder implements context.Context to provide graceful cancellation.
|
||||
|
||||
To decode a stream of JSON, you must call `gojay.Stream.DecodeStream` and pass it a `UnmarshalerStream` implementation.
|
||||
|
||||
```go
|
||||
type UnmarshalerStream interface {
|
||||
UnmarshalStream(*StreamDecoder) error
|
||||
}
|
||||
```
|
||||
|
||||
Example of implementation of stream reading from a WebSocket connection:
|
||||
```go
|
||||
// implement UnmarshalerStream
|
||||
type ChannelStream chan *user
|
||||
|
||||
func (c ChannelStream) UnmarshalStream(dec *gojay.StreamDecoder) error {
|
||||
u := &user{}
|
||||
if err := dec.Object(u); err != nil {
|
||||
return err
|
||||
}
|
||||
c <- u
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
// get our websocket connection
|
||||
origin := "http://localhost/"
|
||||
url := "ws://localhost:12345/ws"
|
||||
ws, err := websocket.Dial(url, "", origin)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// create our channel which will receive our objects
|
||||
streamChan := ChannelStream(make(chan *user))
|
||||
// borrow a decoder
|
||||
dec := gojay.Stream.BorrowDecoder(ws)
|
||||
// start decoding, it will block until a JSON message is decoded from the WebSocket
|
||||
// or until Done channel is closed
|
||||
go dec.DecodeStream(streamChan)
|
||||
for {
|
||||
select {
|
||||
case v := <-streamChan:
|
||||
// Got something from my websocket!
|
||||
log.Println(v)
|
||||
case <-dec.Done():
|
||||
log.Println("finished reading from WebSocket")
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Stream Encoding
|
||||
GoJay ships with a powerful stream encoder part of the Stream API.
|
||||
|
||||
It allows to write continuously to an io.Writer and do JIT encoding of data fed to a channel to allow async consuming. You can set multiple consumers on the channel to be as performant as possible. Consumers are non blocking and are scheduled individually in their own go routine.
|
||||
|
||||
When using the Stream API, the Encoder implements context.Context to provide graceful cancellation.
|
||||
|
||||
To encode a stream of data, you must call `EncodeStream` and pass it a `MarshalerStream` implementation.
|
||||
|
||||
```go
|
||||
type MarshalerStream interface {
|
||||
MarshalStream(enc *gojay.StreamEncoder)
|
||||
}
|
||||
```
|
||||
|
||||
Example of implementation of stream writing to a WebSocket:
|
||||
```go
|
||||
// Our structure which will be pushed to our stream
|
||||
type user struct {
|
||||
id int
|
||||
name string
|
||||
email string
|
||||
}
|
||||
|
||||
func (u *user) MarshalJSONObject(enc *gojay.Encoder) {
|
||||
enc.IntKey("id", u.id)
|
||||
enc.StringKey("name", u.name)
|
||||
enc.StringKey("email", u.email)
|
||||
}
|
||||
func (u *user) IsNil() bool {
|
||||
return u == nil
|
||||
}
|
||||
|
||||
// Our MarshalerStream implementation
|
||||
type StreamChan chan *user
|
||||
|
||||
func (s StreamChan) MarshalStream(enc *gojay.StreamEncoder) {
|
||||
select {
|
||||
case <-enc.Done():
|
||||
return
|
||||
case o := <-s:
|
||||
enc.Object(o)
|
||||
}
|
||||
}
|
||||
|
||||
// Our main function
|
||||
func main() {
|
||||
// get our websocket connection
|
||||
origin := "http://localhost/"
|
||||
url := "ws://localhost:12345/ws"
|
||||
ws, err := websocket.Dial(url, "", origin)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// we borrow an encoder set stdout as the writer,
|
||||
// set the number of consumer to 10
|
||||
// and tell the encoder to separate each encoded element
|
||||
// added to the channel by a new line character
|
||||
enc := gojay.Stream.BorrowEncoder(ws).NConsumer(10).LineDelimited()
|
||||
// instantiate our MarshalerStream
|
||||
s := StreamChan(make(chan *user))
|
||||
// start the stream encoder
|
||||
// will block its goroutine until enc.Cancel(error) is called
|
||||
// or until something is written to the channel
|
||||
go enc.EncodeStream(s)
|
||||
// write to our MarshalerStream
|
||||
for i := 0; i < 1000; i++ {
|
||||
s <- &user{i, "username", "user@email.com"}
|
||||
}
|
||||
// Wait
|
||||
<-enc.Done()
|
||||
}
|
||||
```
|
||||
|
||||
# Unsafe API
|
||||
|
||||
Unsafe API has the same functions than the regular API, it only has `Unmarshal API` for now. It is unsafe because it makes assumptions on the quality of the given JSON.
|
||||
|
||||
If you are not sure if your JSON is valid, don't use the Unsafe API.
|
||||
|
||||
Also, the `Unsafe` API does not copy the buffer when using Unmarshal API, which, in case of string decoding, can lead to data corruption if a byte buffer is reused. Using the `Decode` API makes `Unsafe` API safer as the io.Reader relies on `copy` builtin method and `Decoder` will have its own internal buffer :)
|
||||
|
||||
Access the `Unsafe` API this way:
|
||||
```go
|
||||
gojay.Unsafe.Unmarshal(b, v)
|
||||
```
|
||||
|
||||
|
||||
# Benchmarks
|
||||
|
||||
Benchmarks encode and decode three different data based on size (small, medium, large).
|
||||
|
||||
To run benchmark for decoder:
|
||||
```bash
|
||||
cd $GOPATH/src/github.com/francoispqt/gojay/benchmarks/decoder && make bench
|
||||
```
|
||||
|
||||
To run benchmark for encoder:
|
||||
```bash
|
||||
cd $GOPATH/src/github.com/francoispqt/gojay/benchmarks/encoder && make bench
|
||||
```
|
||||
|
||||
# Benchmark Results
|
||||
## Decode
|
||||
|
||||
<img src="https://images2.imgbox.com/78/01/49OExcPh_o.png" width="500px">
|
||||
|
||||
### Small Payload
|
||||
[benchmark code is here](https://github.com/francoispqt/gojay/blob/master/benchmarks/decoder/decoder_bench_small_test.go)
|
||||
|
||||
[benchmark data is here](https://github.com/francoispqt/gojay/blob/master/benchmarks/benchmarks_small.go)
|
||||
|
||||
| | ns/op | bytes/op | allocs/op |
|
||||
|-----------------|-----------|--------------|-----------|
|
||||
| Std Library | 2547 | 496 | 4 |
|
||||
| JsonIter | 2046 | 312 | 12 |
|
||||
| JsonParser | 1408 | 0 | 0 |
|
||||
| EasyJson | 929 | 240 | 2 |
|
||||
| **GoJay** | **807** | **256** | **2** |
|
||||
| **GoJay-unsafe**| **712** | **112** | **1** |
|
||||
|
||||
### Medium Payload
|
||||
[benchmark code is here](https://github.com/francoispqt/gojay/blob/master/benchmarks/decoder/decoder_bench_medium_test.go)
|
||||
|
||||
[benchmark data is here](https://github.com/francoispqt/gojay/blob/master/benchmarks/benchmarks_medium.go)
|
||||
|
||||
| | ns/op | bytes/op | allocs/op |
|
||||
|-----------------|-----------|----------|-----------|
|
||||
| Std Library | 30148 | 2152 | 496 |
|
||||
| JsonIter | 16309 | 2976 | 80 |
|
||||
| JsonParser | 7793 | 0 | 0 |
|
||||
| EasyJson | 7957 | 232 | 6 |
|
||||
| **GoJay** | **4984** | **2448** | **8** |
|
||||
| **GoJay-unsafe**| **4809** | **144** | **7** |
|
||||
|
||||
### Large Payload
|
||||
[benchmark code is here](https://github.com/francoispqt/gojay/blob/master/benchmarks/decoder/decoder_bench_large_test.go)
|
||||
|
||||
[benchmark data is here](https://github.com/francoispqt/gojay/blob/master/benchmarks/benchmarks_large.go)
|
||||
|
||||
| | ns/op | bytes/op | allocs/op |
|
||||
|-----------------|-----------|-------------|-----------|
|
||||
| JsonIter | 210078 | 41712 | 1136 |
|
||||
| EasyJson | 106626 | 160 | 2 |
|
||||
| JsonParser | 66813 | 0 | 0 |
|
||||
| **GoJay** | **52153** | **31241** | **77** |
|
||||
| **GoJay-unsafe**| **48277** | **2561** | **76** |
|
||||
|
||||
## Encode
|
||||
|
||||
<img src="https://images2.imgbox.com/e9/cc/pnM8c7Gf_o.png" width="500px">
|
||||
|
||||
### Small Struct
|
||||
[benchmark code is here](https://github.com/francoispqt/gojay/blob/master/benchmarks/encoder/encoder_bench_small_test.go)
|
||||
|
||||
[benchmark data is here](https://github.com/francoispqt/gojay/blob/master/benchmarks/benchmarks_small.go)
|
||||
|
||||
| | ns/op | bytes/op | allocs/op |
|
||||
|----------------|----------|--------------|-----------|
|
||||
| Std Library | 1280 | 464 | 3 |
|
||||
| EasyJson | 871 | 944 | 6 |
|
||||
| JsonIter | 866 | 272 | 3 |
|
||||
| **GoJay** | **543** | **112** | **1** |
|
||||
| **GoJay-func** | **347** | **0** | **0** |
|
||||
|
||||
### Medium Struct
|
||||
[benchmark code is here](https://github.com/francoispqt/gojay/blob/master/benchmarks/encoder/encoder_bench_medium_test.go)
|
||||
|
||||
[benchmark data is here](https://github.com/francoispqt/gojay/blob/master/benchmarks/benchmarks_medium.go)
|
||||
|
||||
| | ns/op | bytes/op | allocs/op |
|
||||
|-------------|----------|--------------|-----------|
|
||||
| Std Library | 5006 | 1496 | 25 |
|
||||
| JsonIter | 2232 | 1544 | 20 |
|
||||
| EasyJson | 1997 | 1544 | 19 |
|
||||
| **GoJay** | **1522** | **312** | **14** |
|
||||
|
||||
### Large Struct
|
||||
[benchmark code is here](https://github.com/francoispqt/gojay/blob/master/benchmarks/encoder/encoder_bench_large_test.go)
|
||||
|
||||
[benchmark data is here](https://github.com/francoispqt/gojay/blob/master/benchmarks/benchmarks_large.go)
|
||||
|
||||
| | ns/op | bytes/op | allocs/op |
|
||||
|-------------|-----------|--------------|-----------|
|
||||
| Std Library | 66441 | 20576 | 332 |
|
||||
| JsonIter | 35247 | 20255 | 328 |
|
||||
| EasyJson | 32053 | 15474 | 327 |
|
||||
| **GoJay** | **27847** | **9802** | **318** |
|
||||
|
||||
# Contributing
|
||||
|
||||
Contributions are welcome :)
|
||||
|
||||
If you encounter issues please report it in Github and/or send an email at [francois@parquet.ninja](mailto:francois@parquet.ninja)
|
||||
|
|
@ -1,386 +0,0 @@
|
|||
package gojay
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// UnmarshalJSONArray parses the JSON-encoded data and stores the result in the value pointed to by v.
|
||||
//
|
||||
// v must implement UnmarshalerJSONArray.
|
||||
//
|
||||
// If a JSON value is not appropriate for a given target type, or if a JSON number
|
||||
// overflows the target type, UnmarshalJSONArray skips that field and completes the unmarshaling as best it can.
|
||||
func UnmarshalJSONArray(data []byte, v UnmarshalerJSONArray) error {
|
||||
dec := borrowDecoder(nil, 0)
|
||||
defer dec.Release()
|
||||
dec.data = make([]byte, len(data))
|
||||
copy(dec.data, data)
|
||||
dec.length = len(data)
|
||||
_, err := dec.decodeArray(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if dec.err != nil {
|
||||
return dec.err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSONObject parses the JSON-encoded data and stores the result in the value pointed to by v.
|
||||
//
|
||||
// v must implement UnmarshalerJSONObject.
|
||||
//
|
||||
// If a JSON value is not appropriate for a given target type, or if a JSON number
|
||||
// overflows the target type, UnmarshalJSONObject skips that field and completes the unmarshaling as best it can.
|
||||
func UnmarshalJSONObject(data []byte, v UnmarshalerJSONObject) error {
|
||||
dec := borrowDecoder(nil, 0)
|
||||
defer dec.Release()
|
||||
dec.data = make([]byte, len(data))
|
||||
copy(dec.data, data)
|
||||
dec.length = len(data)
|
||||
_, err := dec.decodeObject(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if dec.err != nil {
|
||||
return dec.err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unmarshal parses the JSON-encoded data and stores the result in the value pointed to by v.
|
||||
// If v is nil, not an implementation of UnmarshalerJSONObject or UnmarshalerJSONArray or not one of the following types:
|
||||
// *string, **string, *int, **int, *int8, **int8, *int16, **int16, *int32, **int32, *int64, **int64, *uint8, **uint8, *uint16, **uint16,
|
||||
// *uint32, **uint32, *uint64, **uint64, *float64, **float64, *float32, **float32, *bool, **bool
|
||||
// Unmarshal returns an InvalidUnmarshalError.
|
||||
//
|
||||
//
|
||||
// If a JSON value is not appropriate for a given target type, or if a JSON number
|
||||
// overflows the target type, Unmarshal skips that field and completes the unmarshaling as best it can.
|
||||
// If no more serious errors are encountered, Unmarshal returns an UnmarshalTypeError describing the earliest such error.
|
||||
// In any case, it's not guaranteed that all the remaining fields following the problematic one will be unmarshaled into the target object.
|
||||
func Unmarshal(data []byte, v interface{}) error {
|
||||
var err error
|
||||
var dec *Decoder
|
||||
switch vt := v.(type) {
|
||||
case *string:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeString(vt)
|
||||
case **string:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeStringNull(vt)
|
||||
case *int:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeInt(vt)
|
||||
case **int:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeIntNull(vt)
|
||||
case *int8:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeInt8(vt)
|
||||
case **int8:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeInt8Null(vt)
|
||||
case *int16:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeInt16(vt)
|
||||
case **int16:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeInt16Null(vt)
|
||||
case *int32:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeInt32(vt)
|
||||
case **int32:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeInt32Null(vt)
|
||||
case *int64:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeInt64(vt)
|
||||
case **int64:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeInt64Null(vt)
|
||||
case *uint8:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeUint8(vt)
|
||||
case **uint8:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeUint8Null(vt)
|
||||
case *uint16:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeUint16(vt)
|
||||
case **uint16:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeUint16Null(vt)
|
||||
case *uint32:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeUint32(vt)
|
||||
case **uint32:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeUint32Null(vt)
|
||||
case *uint64:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeUint64(vt)
|
||||
case **uint64:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeUint64Null(vt)
|
||||
case *float64:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeFloat64(vt)
|
||||
case **float64:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeFloat64Null(vt)
|
||||
case *float32:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeFloat32(vt)
|
||||
case **float32:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeFloat32Null(vt)
|
||||
case *bool:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeBool(vt)
|
||||
case **bool:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeBoolNull(vt)
|
||||
case UnmarshalerJSONObject:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = make([]byte, len(data))
|
||||
copy(dec.data, data)
|
||||
_, err = dec.decodeObject(vt)
|
||||
case UnmarshalerJSONArray:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = make([]byte, len(data))
|
||||
copy(dec.data, data)
|
||||
_, err = dec.decodeArray(vt)
|
||||
case *interface{}:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = make([]byte, len(data))
|
||||
copy(dec.data, data)
|
||||
err = dec.decodeInterface(vt)
|
||||
default:
|
||||
return InvalidUnmarshalError(fmt.Sprintf(invalidUnmarshalErrorMsg, vt))
|
||||
}
|
||||
defer dec.Release()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return dec.err
|
||||
}
|
||||
|
||||
// UnmarshalerJSONObject is the interface to implement to decode a JSON Object.
|
||||
type UnmarshalerJSONObject interface {
|
||||
UnmarshalJSONObject(*Decoder, string) error
|
||||
NKeys() int
|
||||
}
|
||||
|
||||
// UnmarshalerJSONArray is the interface to implement to decode a JSON Array.
|
||||
type UnmarshalerJSONArray interface {
|
||||
UnmarshalJSONArray(*Decoder) error
|
||||
}
|
||||
|
||||
// A Decoder reads and decodes JSON values from an input stream.
|
||||
type Decoder struct {
|
||||
r io.Reader
|
||||
data []byte
|
||||
err error
|
||||
isPooled byte
|
||||
called byte
|
||||
child byte
|
||||
cursor int
|
||||
length int
|
||||
keysDone int
|
||||
arrayIndex int
|
||||
}
|
||||
|
||||
// Decode reads the next JSON-encoded value from the decoder's input (io.Reader) and stores it in the value pointed to by v.
|
||||
//
|
||||
// See the documentation for Unmarshal for details about the conversion of JSON into a Go value.
|
||||
// The differences between Decode and Unmarshal are:
|
||||
// - Decode reads from an io.Reader in the Decoder, whereas Unmarshal reads from a []byte
|
||||
// - Decode leaves to the user the option of borrowing and releasing a Decoder, whereas Unmarshal internally always borrows a Decoder and releases it when the unmarshaling is completed
|
||||
func (dec *Decoder) Decode(v interface{}) error {
|
||||
if dec.isPooled == 1 {
|
||||
panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder"))
|
||||
}
|
||||
var err error
|
||||
switch vt := v.(type) {
|
||||
case *string:
|
||||
err = dec.decodeString(vt)
|
||||
case **string:
|
||||
err = dec.decodeStringNull(vt)
|
||||
case *int:
|
||||
err = dec.decodeInt(vt)
|
||||
case **int:
|
||||
err = dec.decodeIntNull(vt)
|
||||
case *int8:
|
||||
err = dec.decodeInt8(vt)
|
||||
case **int8:
|
||||
err = dec.decodeInt8Null(vt)
|
||||
case *int16:
|
||||
err = dec.decodeInt16(vt)
|
||||
case **int16:
|
||||
err = dec.decodeInt16Null(vt)
|
||||
case *int32:
|
||||
err = dec.decodeInt32(vt)
|
||||
case **int32:
|
||||
err = dec.decodeInt32Null(vt)
|
||||
case *int64:
|
||||
err = dec.decodeInt64(vt)
|
||||
case **int64:
|
||||
err = dec.decodeInt64Null(vt)
|
||||
case *uint8:
|
||||
err = dec.decodeUint8(vt)
|
||||
case **uint8:
|
||||
err = dec.decodeUint8Null(vt)
|
||||
case *uint16:
|
||||
err = dec.decodeUint16(vt)
|
||||
case **uint16:
|
||||
err = dec.decodeUint16Null(vt)
|
||||
case *uint32:
|
||||
err = dec.decodeUint32(vt)
|
||||
case **uint32:
|
||||
err = dec.decodeUint32Null(vt)
|
||||
case *uint64:
|
||||
err = dec.decodeUint64(vt)
|
||||
case **uint64:
|
||||
err = dec.decodeUint64Null(vt)
|
||||
case *float64:
|
||||
err = dec.decodeFloat64(vt)
|
||||
case **float64:
|
||||
err = dec.decodeFloat64Null(vt)
|
||||
case *float32:
|
||||
err = dec.decodeFloat32(vt)
|
||||
case **float32:
|
||||
err = dec.decodeFloat32Null(vt)
|
||||
case *bool:
|
||||
err = dec.decodeBool(vt)
|
||||
case **bool:
|
||||
err = dec.decodeBoolNull(vt)
|
||||
case UnmarshalerJSONObject:
|
||||
_, err = dec.decodeObject(vt)
|
||||
case UnmarshalerJSONArray:
|
||||
_, err = dec.decodeArray(vt)
|
||||
case *EmbeddedJSON:
|
||||
err = dec.decodeEmbeddedJSON(vt)
|
||||
case *interface{}:
|
||||
err = dec.decodeInterface(vt)
|
||||
default:
|
||||
return InvalidUnmarshalError(fmt.Sprintf(invalidUnmarshalErrorMsg, vt))
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return dec.err
|
||||
}
|
||||
|
||||
// Non exported
|
||||
|
||||
func isDigit(b byte) bool {
|
||||
switch b {
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||