├── .gitignore ├── .travis.yml ├── Gopkg.lock ├── Gopkg.toml ├── LICENSE ├── Makefile ├── README.md ├── benchmarks ├── benchmarks_large.go ├── benchmarks_large_easyjson.go ├── benchmarks_medium.go ├── benchmarks_medium_easyjson.go ├── benchmarks_small.go ├── benchmarks_small_easyjson.go ├── decoder │ ├── .gitignore │ ├── Makefile │ ├── decoder.go │ ├── decoder_bench_float_test.go │ ├── decoder_bench_large_test.go │ ├── decoder_bench_medium_test.go │ ├── decoder_bench_small_test.go │ ├── decoder_large_test.go │ ├── decoder_medium_test.go │ └── decoder_small_test.go └── encoder │ ├── .gitignore │ ├── Makefile │ ├── encoder_bench_large_test.go │ ├── encoder_bench_medium_test.go │ ├── encoder_bench_small_test.go │ ├── encoder_large_test.go │ ├── encoder_medium_test.go │ └── encoder_small_test.go ├── decode.go ├── decode_array.go ├── decode_array_test.go ├── decode_bool.go ├── decode_bool_test.go ├── decode_embedded_json.go ├── decode_embedded_json_test.go ├── decode_example_test.go ├── decode_interface.go ├── decode_interface_test.go ├── decode_number.go ├── decode_number_float.go ├── decode_number_float_test.go ├── decode_number_int.go ├── decode_number_int_test.go ├── decode_number_test.go ├── decode_number_uint.go ├── decode_number_uint_test.go ├── decode_object.go ├── decode_object_test.go ├── decode_pool.go ├── decode_pool_test.go ├── decode_slice.go ├── decode_slice_test.go ├── decode_sqlnull.go ├── decode_sqlnull_test.go ├── decode_stream.go ├── decode_stream_pool.go ├── decode_stream_pool_test.go ├── decode_stream_test.go ├── decode_string.go ├── decode_string_test.go ├── decode_string_unicode.go ├── decode_test.go ├── decode_time.go ├── decode_time_test.go ├── decode_unsafe.go ├── decode_unsafe_test.go ├── encode.go ├── encode_array.go ├── encode_array_test.go ├── encode_bool.go ├── encode_bool_test.go ├── encode_builder.go ├── encode_builder_test.go ├── encode_embedded_json.go ├── encode_embedded_json_test.go ├── encode_example_test.go ├── encode_interface.go ├── encode_interface_test.go ├── encode_null.go ├── encode_null_test.go ├── encode_number.go ├── encode_number_float.go ├── encode_number_float_test.go ├── encode_number_int.go ├── encode_number_int_test.go ├── encode_number_test.go ├── encode_number_uint.go ├── encode_number_uint_test.go ├── encode_object.go ├── encode_object_test.go ├── encode_pool.go ├── encode_pool_test.go ├── encode_slice.go ├── encode_slice_test.go ├── encode_sqlnull.go ├── encode_sqlnull_test.go ├── encode_stream.go ├── encode_stream_pool.go ├── encode_stream_test.go ├── encode_string.go ├── encode_string_test.go ├── encode_test.go ├── encode_time.go ├── encode_time_test.go ├── errors.go ├── examples ├── encode-decode-map │ └── main.go ├── fuzz │ ├── Makefile │ └── main.go ├── http-benchmarks │ ├── Makefile │ ├── README.md │ ├── gojay │ │ └── main.go │ ├── post.lua │ └── standard │ │ └── main.go ├── http-json │ └── main.go └── websocket │ ├── client │ └── client.go │ ├── comm │ └── comm.go │ ├── main.go │ └── server │ └── server.go ├── go.mod ├── go.sum ├── gojay.go ├── gojay.png ├── gojay ├── README.md ├── codegen │ ├── field.go │ ├── generator.go │ ├── generator_test.go │ ├── helper.go │ ├── options.go │ ├── struct.go │ ├── template.go │ ├── template_test.go │ └── test │ │ ├── annotated_struct │ │ ├── encoding.go │ │ ├── encoding_test.go │ │ ├── message.go │ │ └── sub_message.go │ │ ├── basic_struct │ │ ├── encoding.go │ │ ├── encoding_test.go │ │ ├── message.go │ │ └── sub_message.go │ │ ├── embedded_struct │ │ ├── encoding.go │ │ ├── encoding_test.go │ │ ├── message.go │ │ └── sub_message.go │ │ └── pooled_struct │ │ ├── encoding.go │ │ ├── encoding_test.go │ │ ├── message.go │ │ └── sub_message.go └── gojay.go ├── gojay_example_test.go └── gojay_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | *.out 3 | *.log 4 | *.test 5 | .vscode 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - "1.10.x" 5 | - "1.11.x" 6 | - "1.12.x" 7 | 8 | script: 9 | - go get github.com/golang/dep/cmd/dep github.com/stretchr/testify 10 | - dep ensure -v -vendor-only 11 | - go test ./gojay/codegen/test/... -race 12 | - go test -race -coverprofile=coverage.txt -covermode=atomic 13 | 14 | after_success: 15 | - bash <(curl -s https://codecov.io/bash) 16 | -------------------------------------------------------------------------------- /Gopkg.lock: -------------------------------------------------------------------------------- 1 | # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. 2 | 3 | 4 | [[projects]] 5 | digest = "1:1a37f9f2ae10d161d9688fb6008ffa14e1631e5068cc3e9698008b9e8d40d575" 6 | name = "cloud.google.com/go" 7 | packages = ["compute/metadata"] 8 | pruneopts = "" 9 | revision = "457ea5c15ccf3b87db582c450e80101989da35f7" 10 | version = "v0.40.0" 11 | 12 | [[projects]] 13 | digest = "1:968d8903d598e3fae738325d3410f33f07ea6a2b9ee5591e9c262ee37df6845a" 14 | name = "github.com/go-errors/errors" 15 | packages = ["."] 16 | pruneopts = "" 17 | revision = "a6af135bd4e28680facf08a3d206b454abc877a4" 18 | version = "v1.0.1" 19 | 20 | [[projects]] 21 | digest = "1:529d738b7976c3848cae5cf3a8036440166835e389c1f617af701eeb12a0518d" 22 | name = "github.com/golang/protobuf" 23 | packages = ["proto"] 24 | pruneopts = "" 25 | revision = "b5d812f8a3706043e23a9cd5babf2e5423744d30" 26 | version = "v1.3.1" 27 | 28 | [[projects]] 29 | branch = "master" 30 | digest = "1:cae59d7b8243c671c9f544965522ba35c0fec48ee80adb9f1400cd2f33abbbec" 31 | name = "github.com/mailru/easyjson" 32 | packages = [ 33 | ".", 34 | "buffer", 35 | "jlexer", 36 | "jwriter", 37 | ] 38 | pruneopts = "" 39 | revision = "1ea4449da9834f4d333f1cc461c374aea217d249" 40 | 41 | [[projects]] 42 | digest = "1:1d7e1867c49a6dd9856598ef7c3123604ea3daabf5b83f303ff457bcbc410b1d" 43 | name = "github.com/pkg/errors" 44 | packages = ["."] 45 | pruneopts = "" 46 | revision = "ba968bfe8b2f7e042a574c888954fccecfa385b4" 47 | version = "v0.8.1" 48 | 49 | [[projects]] 50 | digest = "1:8d4bbd8ab012efc77ab6b97286f2aff262bcdeac9803bb57d75cf7d0a5e6a877" 51 | name = "github.com/viant/assertly" 52 | packages = ["."] 53 | pruneopts = "" 54 | revision = "04f45e0aeb6f3455884877b047a97bcc95dc9493" 55 | version = "v0.4.8" 56 | 57 | [[projects]] 58 | digest = "1:5913451bc2d274673c0716efe226a137625740cd9380641f4d8300ff4f2d82a0" 59 | name = "github.com/viant/toolbox" 60 | packages = [ 61 | ".", 62 | "cred", 63 | "data", 64 | "storage", 65 | "url", 66 | ] 67 | pruneopts = "" 68 | revision = "1be8e4d172138324f40d55ea61a2aeab0c5ce864" 69 | version = "v0.24.0" 70 | 71 | [[projects]] 72 | branch = "master" 73 | digest = "1:9d150270ca2c3356f2224a0878daa1652e4d0b25b345f18b4f6e156cc4b8ec5e" 74 | name = "golang.org/x/crypto" 75 | packages = [ 76 | "blowfish", 77 | "curve25519", 78 | "ed25519", 79 | "ed25519/internal/edwards25519", 80 | "internal/chacha20", 81 | "internal/subtle", 82 | "poly1305", 83 | "ssh", 84 | ] 85 | pruneopts = "" 86 | revision = "f99c8df09eb5bff426315721bfa5f16a99cad32c" 87 | 88 | [[projects]] 89 | branch = "master" 90 | digest = "1:5a56f211e7c12a65c5585c629457a2fb91d8719844ee8fab92727ea8adb5721c" 91 | name = "golang.org/x/net" 92 | packages = [ 93 | "context", 94 | "context/ctxhttp", 95 | "websocket", 96 | ] 97 | pruneopts = "" 98 | revision = "461777fb6f67e8cb9d70cda16573678d085a74cf" 99 | 100 | [[projects]] 101 | branch = "master" 102 | digest = "1:01bdbbc604dcd5afb6f66a717f69ad45e9643c72d5bc11678d44ffa5c50f9e42" 103 | name = "golang.org/x/oauth2" 104 | packages = [ 105 | ".", 106 | "google", 107 | "internal", 108 | "jws", 109 | "jwt", 110 | ] 111 | pruneopts = "" 112 | revision = "0f29369cfe4552d0e4bcddc57cc75f4d7e672a33" 113 | 114 | [[projects]] 115 | branch = "master" 116 | digest = "1:8ddb956f67d4c176abbbc42b7514aaeaf9ea30daa24e27d2cf30ad82f9334a2c" 117 | name = "golang.org/x/sys" 118 | packages = ["cpu"] 119 | pruneopts = "" 120 | revision = "1e42afee0f762ed3d76e6dd942e4181855fd1849" 121 | 122 | [[projects]] 123 | digest = "1:47f391ee443f578f01168347818cb234ed819521e49e4d2c8dd2fb80d48ee41a" 124 | name = "google.golang.org/appengine" 125 | packages = [ 126 | ".", 127 | "internal", 128 | "internal/app_identity", 129 | "internal/base", 130 | "internal/datastore", 131 | "internal/log", 132 | "internal/modules", 133 | "internal/remote_api", 134 | "internal/urlfetch", 135 | "urlfetch", 136 | ] 137 | pruneopts = "" 138 | revision = "b2f4a3cf3c67576a2ee09e1fe62656a5086ce880" 139 | version = "v1.6.1" 140 | 141 | [[projects]] 142 | digest = "1:cedccf16b71e86db87a24f8d4c70b0a855872eb967cb906a66b95de56aefbd0d" 143 | name = "gopkg.in/yaml.v2" 144 | packages = ["."] 145 | pruneopts = "" 146 | revision = "51d6538a90f86fe93ac480b35f37b2be17fef232" 147 | version = "v2.2.2" 148 | 149 | [solve-meta] 150 | analyzer-name = "dep" 151 | analyzer-version = 1 152 | input-imports = [ 153 | "github.com/go-errors/errors", 154 | "github.com/mailru/easyjson", 155 | "github.com/mailru/easyjson/jlexer", 156 | "github.com/mailru/easyjson/jwriter", 157 | "github.com/viant/assertly", 158 | "github.com/viant/toolbox", 159 | "github.com/viant/toolbox/url", 160 | "golang.org/x/net/websocket", 161 | ] 162 | solver-name = "gps-cdcl" 163 | solver-version = 1 164 | -------------------------------------------------------------------------------- /Gopkg.toml: -------------------------------------------------------------------------------- 1 | # Gopkg.toml example 2 | # 3 | # Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md 4 | # for detailed Gopkg.toml documentation. 5 | # 6 | # required = ["github.com/user/thing/cmd/thing"] 7 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] 8 | # 9 | # [[constraint]] 10 | # name = "github.com/user/project" 11 | # version = "1.0.0" 12 | # 13 | # [[constraint]] 14 | # name = "github.com/user/project2" 15 | # branch = "dev" 16 | # source = "github.com/myfork/project2" 17 | # 18 | # [[override]] 19 | # name = "github.com/x/y" 20 | # version = "2.4.0" 21 | 22 | 23 | ignored = ["github.com/francoispqt/benchmarks*","github.com/stretchr/testify*","github.com/stretchr/testify","github.com/json-iterator/go","github.com/buger/jsonparser"] 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 gojay 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test 2 | test: 3 | go test -race -run=^Test -v 4 | 5 | .PHONY: cover 6 | cover: 7 | go test -coverprofile=coverage.out -covermode=atomic 8 | 9 | .PHONY: coverhtml 10 | coverhtml: 11 | go tool cover -html=coverage.out -------------------------------------------------------------------------------- /benchmarks/benchmarks_small.go: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | 3 | import "github.com/francoispqt/gojay" 4 | 5 | var SmallFixture = []byte(`{"st": 1,"sid": 486,"tt": "active","gr": 0,"uuid": "de305d54-75b4-431b-adb2-eb6b9e546014","ip": "127.0.0.1","ua": "user_agent","tz": -6,"v": 1}`) 6 | 7 | //easyjson:json 8 | type SmallPayload struct { 9 | St int 10 | Sid int 11 | Tt string 12 | Gr int 13 | Uuid string 14 | Ip string 15 | Ua string 16 | Tz int 17 | V int 18 | } 19 | 20 | func (t *SmallPayload) MarshalJSONObject(enc *gojay.Encoder) { 21 | enc.AddIntKey("st", t.St) 22 | enc.AddIntKey("sid", t.Sid) 23 | enc.AddStringKey("tt", t.Tt) 24 | enc.AddIntKey("gr", t.Gr) 25 | enc.AddStringKey("uuid", t.Uuid) 26 | enc.AddStringKey("ip", t.Ip) 27 | enc.AddStringKey("ua", t.Ua) 28 | enc.AddIntKey("tz", t.Tz) 29 | enc.AddIntKey("v", t.V) 30 | } 31 | 32 | func (t *SmallPayload) IsNil() bool { 33 | return t == nil 34 | } 35 | 36 | func (t *SmallPayload) UnmarshalJSONObject(dec *gojay.Decoder, key string) error { 37 | switch key { 38 | case "st": 39 | return dec.AddInt(&t.St) 40 | case "sid": 41 | return dec.AddInt(&t.Sid) 42 | case "gr": 43 | return dec.AddInt(&t.Gr) 44 | case "tz": 45 | return dec.AddInt(&t.Tz) 46 | case "v": 47 | return dec.AddInt(&t.V) 48 | case "tt": 49 | return dec.AddString(&t.Tt) 50 | case "uuid": 51 | return dec.AddString(&t.Uuid) 52 | case "ip": 53 | return dec.AddString(&t.Ip) 54 | case "ua": 55 | return dec.AddString(&t.Ua) 56 | } 57 | return nil 58 | } 59 | 60 | func (t *SmallPayload) NKeys() int { 61 | return 9 62 | } 63 | 64 | func NewSmallPayload() *SmallPayload { 65 | return &SmallPayload{ 66 | St: 1, 67 | Sid: 2, 68 | Tt: "TestString", 69 | Gr: 4, 70 | Uuid: "8f9a65eb-4807-4d57-b6e0-bda5d62f1429", 71 | Ip: "127.0.0.1", 72 | Ua: "Mozilla", 73 | Tz: 8, 74 | V: 6, 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /benchmarks/benchmarks_small_easyjson.go: -------------------------------------------------------------------------------- 1 | // Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT. 2 | 3 | package benchmarks 4 | 5 | import ( 6 | json "encoding/json" 7 | easyjson "github.com/mailru/easyjson" 8 | jlexer "github.com/mailru/easyjson/jlexer" 9 | jwriter "github.com/mailru/easyjson/jwriter" 10 | ) 11 | 12 | // suppress unused package warning 13 | var ( 14 | _ *json.RawMessage 15 | _ *jlexer.Lexer 16 | _ *jwriter.Writer 17 | _ easyjson.Marshaler 18 | ) 19 | 20 | func easyjson890029d8DecodeGithubComFrancoispqtGojayBenchmarks(in *jlexer.Lexer, out *SmallPayload) { 21 | isTopLevel := in.IsStart() 22 | if in.IsNull() { 23 | if isTopLevel { 24 | in.Consumed() 25 | } 26 | in.Skip() 27 | return 28 | } 29 | in.Delim('{') 30 | for !in.IsDelim('}') { 31 | key := in.UnsafeString() 32 | in.WantColon() 33 | if in.IsNull() { 34 | in.Skip() 35 | in.WantComma() 36 | continue 37 | } 38 | switch key { 39 | case "St": 40 | out.St = int(in.Int()) 41 | case "Sid": 42 | out.Sid = int(in.Int()) 43 | case "Tt": 44 | out.Tt = string(in.String()) 45 | case "Gr": 46 | out.Gr = int(in.Int()) 47 | case "Uuid": 48 | out.Uuid = string(in.String()) 49 | case "Ip": 50 | out.Ip = string(in.String()) 51 | case "Ua": 52 | out.Ua = string(in.String()) 53 | case "Tz": 54 | out.Tz = int(in.Int()) 55 | case "V": 56 | out.V = int(in.Int()) 57 | default: 58 | in.SkipRecursive() 59 | } 60 | in.WantComma() 61 | } 62 | in.Delim('}') 63 | if isTopLevel { 64 | in.Consumed() 65 | } 66 | } 67 | func easyjson890029d8EncodeGithubComFrancoispqtGojayBenchmarks(out *jwriter.Writer, in SmallPayload) { 68 | out.RawByte('{') 69 | first := true 70 | _ = first 71 | { 72 | const prefix string = ",\"St\":" 73 | if first { 74 | first = false 75 | out.RawString(prefix[1:]) 76 | } else { 77 | out.RawString(prefix) 78 | } 79 | out.Int(int(in.St)) 80 | } 81 | { 82 | const prefix string = ",\"Sid\":" 83 | if first { 84 | first = false 85 | out.RawString(prefix[1:]) 86 | } else { 87 | out.RawString(prefix) 88 | } 89 | out.Int(int(in.Sid)) 90 | } 91 | { 92 | const prefix string = ",\"Tt\":" 93 | if first { 94 | first = false 95 | out.RawString(prefix[1:]) 96 | } else { 97 | out.RawString(prefix) 98 | } 99 | out.String(string(in.Tt)) 100 | } 101 | { 102 | const prefix string = ",\"Gr\":" 103 | if first { 104 | first = false 105 | out.RawString(prefix[1:]) 106 | } else { 107 | out.RawString(prefix) 108 | } 109 | out.Int(int(in.Gr)) 110 | } 111 | { 112 | const prefix string = ",\"Uuid\":" 113 | if first { 114 | first = false 115 | out.RawString(prefix[1:]) 116 | } else { 117 | out.RawString(prefix) 118 | } 119 | out.String(string(in.Uuid)) 120 | } 121 | { 122 | const prefix string = ",\"Ip\":" 123 | if first { 124 | first = false 125 | out.RawString(prefix[1:]) 126 | } else { 127 | out.RawString(prefix) 128 | } 129 | out.String(string(in.Ip)) 130 | } 131 | { 132 | const prefix string = ",\"Ua\":" 133 | if first { 134 | first = false 135 | out.RawString(prefix[1:]) 136 | } else { 137 | out.RawString(prefix) 138 | } 139 | out.String(string(in.Ua)) 140 | } 141 | { 142 | const prefix string = ",\"Tz\":" 143 | if first { 144 | first = false 145 | out.RawString(prefix[1:]) 146 | } else { 147 | out.RawString(prefix) 148 | } 149 | out.Int(int(in.Tz)) 150 | } 151 | { 152 | const prefix string = ",\"V\":" 153 | if first { 154 | first = false 155 | out.RawString(prefix[1:]) 156 | } else { 157 | out.RawString(prefix) 158 | } 159 | out.Int(int(in.V)) 160 | } 161 | out.RawByte('}') 162 | } 163 | 164 | // MarshalJSON supports json.Marshaler interface 165 | func (v SmallPayload) MarshalJSON() ([]byte, error) { 166 | w := jwriter.Writer{} 167 | easyjson890029d8EncodeGithubComFrancoispqtGojayBenchmarks(&w, v) 168 | return w.Buffer.BuildBytes(), w.Error 169 | } 170 | 171 | // MarshalEasyJSON supports easyjson.Marshaler interface 172 | func (v SmallPayload) MarshalEasyJSON(w *jwriter.Writer) { 173 | easyjson890029d8EncodeGithubComFrancoispqtGojayBenchmarks(w, v) 174 | } 175 | 176 | // UnmarshalJSON supports json.Unmarshaler interface 177 | func (v *SmallPayload) UnmarshalJSON(data []byte) error { 178 | r := jlexer.Lexer{Data: data} 179 | easyjson890029d8DecodeGithubComFrancoispqtGojayBenchmarks(&r, v) 180 | return r.Error() 181 | } 182 | 183 | // UnmarshalEasyJSON supports easyjson.Unmarshaler interface 184 | func (v *SmallPayload) UnmarshalEasyJSON(l *jlexer.Lexer) { 185 | easyjson890029d8DecodeGithubComFrancoispqtGojayBenchmarks(l, v) 186 | } 187 | -------------------------------------------------------------------------------- /benchmarks/decoder/.gitignore: -------------------------------------------------------------------------------- 1 | vendor/* -------------------------------------------------------------------------------- /benchmarks/decoder/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test 2 | test: 3 | go test -run=^Test -v 4 | 5 | .PHONY: bench 6 | bench: 7 | go test -benchmem -run=^$ github.com/francoispqt/gojay/benchmarks/decoder -bench ^Benchmark 8 | 9 | .PHONY: benchtrace 10 | benchtrace: 11 | go test -c & GODEBUG=allocfreetrace=1 ./decoder.test -test.run=none -test.bench=^Benchmark -test.benchtime=10ms 2>trace.log 12 | 13 | .PHONY: benchcpu 14 | benchcpu: 15 | go test -benchmem -run=^$ github.com/francoispqt/gojay/benchmarks/decoder -bench ^Benchmark -cpuprofile cpu.out 16 | 17 | .PHONY: testtrace 18 | testtrace: 19 | go test -benchmem -run=^$ github.com/francoispqt/gojay/benchmarks/decoder -bench ^Benchmark -trace trace.out 20 | 21 | .PHONY: benchgojay 22 | benchgojay: 23 | go test -benchmem -run=^BenchmarkGoJay -bench=^BenchmarkGoJay -benchtime=30ms 24 | 25 | .PHONY: benchgojaycpu 26 | benchgojaycpu: 27 | go test -benchmem -run=^BenchmarkGoJay -bench=^BenchmarkGoJay -benchtime=30ms -cpuprofile cpu.out 28 | 29 | .PHONY: benchjsoniter 30 | benchjsoniter: 31 | go test -benchmem -run=^BenchmarkJsonIter -bench=^BenchmarkJsonIter -benchtime=30ms 32 | 33 | .PHONY: benchjsonparser 34 | benchjsonparser: 35 | go test -benchmem -run=^BenchmarkJsonParser -bench=^BenchmarkJsonParser -benchtime=30ms -------------------------------------------------------------------------------- /benchmarks/decoder/decoder.go: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | -------------------------------------------------------------------------------- /benchmarks/decoder/decoder_bench_float_test.go: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | 7 | "github.com/francoispqt/gojay" 8 | ) 9 | 10 | var bigf = []byte(`0.00058273999999999999`) 11 | 12 | // BenchmarkBigFloatEncodingJSON decodes a big float with the standard package 13 | func BenchmarkBigFloatEncodingJSON(b *testing.B) { 14 | b.ReportAllocs() 15 | for n := 0; n < b.N; n++ { 16 | var f float64 17 | var _ = json.Unmarshal(bigf, &f) 18 | } 19 | } 20 | 21 | // BenchmarkBigFloatGojay decodes a big float with gojay 22 | func BenchmarkBigFloatGojay(b *testing.B) { 23 | b.ReportAllocs() 24 | for n := 0; n < b.N; n++ { 25 | var f float64 26 | var _ = gojay.Unmarshal(bigf, &f) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /benchmarks/decoder/decoder_bench_large_test.go: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/buger/jsonparser" 7 | "github.com/francoispqt/gojay" 8 | "github.com/francoispqt/gojay/benchmarks" 9 | jsoniter "github.com/json-iterator/go" 10 | "github.com/mailru/easyjson" 11 | ) 12 | 13 | func BenchmarkJsonParserDecodeObjLarge(b *testing.B) { 14 | b.ReportAllocs() 15 | for i := 0; i < b.N; i++ { 16 | jsonparser.ArrayEach(benchmarks.LargeFixture, func(value []byte, dataType jsonparser.ValueType, offset int, err error) { 17 | jsonparser.Get(value, "username") 18 | nothing() 19 | }, "users") 20 | 21 | jsonparser.ArrayEach(benchmarks.LargeFixture, func(value []byte, dataType jsonparser.ValueType, offset int, err error) { 22 | jsonparser.GetInt(value, "id") 23 | jsonparser.Get(value, "slug") 24 | nothing() 25 | }, "topics", "topics") 26 | } 27 | } 28 | 29 | func BenchmarkJsonIterDecodeObjLarge(b *testing.B) { 30 | b.ReportAllocs() 31 | for i := 0; i < b.N; i++ { 32 | result := benchmarks.LargePayload{} 33 | jsoniter.Unmarshal(benchmarks.LargeFixture, &result) 34 | } 35 | } 36 | 37 | func BenchmarkEasyJsonDecodeObjLarge(b *testing.B) { 38 | b.ReportAllocs() 39 | for i := 0; i < b.N; i++ { 40 | result := benchmarks.LargePayload{} 41 | easyjson.Unmarshal(benchmarks.LargeFixture, &result) 42 | } 43 | } 44 | 45 | func BenchmarkGoJayDecodeObjLarge(b *testing.B) { 46 | b.ReportAllocs() 47 | for i := 0; i < b.N; i++ { 48 | result := benchmarks.LargePayload{} 49 | gojay.UnmarshalJSONObject(benchmarks.LargeFixture, &result) 50 | } 51 | } 52 | 53 | func BenchmarkGoJayUnsafeDecodeObjLarge(b *testing.B) { 54 | b.ReportAllocs() 55 | for i := 0; i < b.N; i++ { 56 | result := benchmarks.LargePayload{} 57 | gojay.Unsafe.UnmarshalJSONObject(benchmarks.LargeFixture, &result) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /benchmarks/decoder/decoder_bench_medium_test.go: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | 7 | "github.com/buger/jsonparser" 8 | "github.com/francoispqt/gojay" 9 | "github.com/francoispqt/gojay/benchmarks" 10 | jsoniter "github.com/json-iterator/go" 11 | "github.com/mailru/easyjson" 12 | ) 13 | 14 | func BenchmarkJsonIterDecodeObjMedium(b *testing.B) { 15 | b.ReportAllocs() 16 | for n := 0; n < b.N; n++ { 17 | result := benchmarks.MediumPayload{} 18 | jsoniter.Unmarshal(benchmarks.MediumFixture, &result) 19 | } 20 | } 21 | 22 | /* 23 | github.com/buger/jsonparser 24 | */ 25 | func BenchmarkJSONParserDecodeObjMedium(b *testing.B) { 26 | b.ReportAllocs() 27 | for i := 0; i < b.N; i++ { 28 | jsonparser.Get(benchmarks.MediumFixture, "person", "name", "fullName") 29 | jsonparser.GetInt(benchmarks.MediumFixture, "person", "github", "followers") 30 | jsonparser.Get(benchmarks.MediumFixture, "company") 31 | 32 | jsonparser.ArrayEach(benchmarks.MediumFixture, func(value []byte, dataType jsonparser.ValueType, offset int, err error) { 33 | jsonparser.Get(value, "url") 34 | nothing() 35 | }, "person", "gravatar", "avatars") 36 | } 37 | } 38 | 39 | func BenchmarkEncodingJsonStructMedium(b *testing.B) { 40 | for i := 0; i < b.N; i++ { 41 | var data = benchmarks.MediumPayload{} 42 | json.Unmarshal(benchmarks.MediumFixture, &data) 43 | } 44 | } 45 | 46 | func BenchmarkEasyJsonDecodeObjMedium(b *testing.B) { 47 | b.ReportAllocs() 48 | for i := 0; i < b.N; i++ { 49 | result := benchmarks.MediumPayload{} 50 | easyjson.Unmarshal(benchmarks.MediumFixture, &result) 51 | } 52 | } 53 | func BenchmarkGoJayDecodeObjMedium(b *testing.B) { 54 | b.ReportAllocs() 55 | for i := 0; i < b.N; i++ { 56 | result := benchmarks.MediumPayload{} 57 | err := gojay.UnmarshalJSONObject(benchmarks.MediumFixture, &result) 58 | if err != nil { 59 | b.Error(err) 60 | } 61 | } 62 | } 63 | func BenchmarkGoJayUnsafeDecodeObjMedium(b *testing.B) { 64 | b.ReportAllocs() 65 | for i := 0; i < b.N; i++ { 66 | result := benchmarks.MediumPayload{} 67 | err := gojay.Unsafe.UnmarshalJSONObject(benchmarks.MediumFixture, &result) 68 | if err != nil { 69 | b.Error(err) 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /benchmarks/decoder/decoder_bench_small_test.go: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | 3 | import ( 4 | "encoding/json" 5 | _ "fmt" 6 | "testing" 7 | 8 | "github.com/buger/jsonparser" 9 | "github.com/francoispqt/gojay" 10 | "github.com/francoispqt/gojay/benchmarks" 11 | jsoniter "github.com/json-iterator/go" 12 | "github.com/mailru/easyjson" 13 | ) 14 | 15 | func BenchmarkJSONDecodeObjSmall(b *testing.B) { 16 | b.ReportAllocs() 17 | for n := 0; n < b.N; n++ { 18 | result := benchmarks.SmallPayload{} 19 | json.Unmarshal(benchmarks.SmallFixture, &result) 20 | } 21 | } 22 | 23 | func nothing(_ ...interface{}) {} 24 | func BenchmarkJSONParserSmall(b *testing.B) { 25 | b.ReportAllocs() 26 | for i := 0; i < b.N; i++ { 27 | jsonparser.GetInt(benchmarks.SmallFixture, "tz") 28 | jsonparser.GetInt(benchmarks.SmallFixture, "v") 29 | jsonparser.GetInt(benchmarks.SmallFixture, "sid") 30 | jsonparser.GetInt(benchmarks.SmallFixture, "st") 31 | jsonparser.GetInt(benchmarks.SmallFixture, "gr") 32 | jsonparser.Get(benchmarks.SmallFixture, "uuid") 33 | jsonparser.Get(benchmarks.SmallFixture, "ua") 34 | 35 | nothing() 36 | } 37 | } 38 | 39 | func BenchmarkJsonIterDecodeObjSmall(b *testing.B) { 40 | b.ReportAllocs() 41 | for n := 0; n < b.N; n++ { 42 | result := benchmarks.SmallPayload{} 43 | jsoniter.Unmarshal(benchmarks.SmallFixture, &result) 44 | } 45 | } 46 | 47 | func BenchmarkEasyJsonDecodeObjSmall(b *testing.B) { 48 | b.ReportAllocs() 49 | for i := 0; i < b.N; i++ { 50 | result := benchmarks.SmallPayload{} 51 | easyjson.Unmarshal(benchmarks.SmallFixture, &result) 52 | } 53 | } 54 | 55 | func BenchmarkGoJayDecodeObjSmall(b *testing.B) { 56 | b.ReportAllocs() 57 | for n := 0; n < b.N; n++ { 58 | result := benchmarks.SmallPayload{} 59 | gojay.UnmarshalJSONObject(benchmarks.SmallFixture, &result) 60 | } 61 | } 62 | 63 | func BenchmarkGoJayUnsafeDecodeObjSmall(b *testing.B) { 64 | b.ReportAllocs() 65 | for i := 0; i < b.N; i++ { 66 | result := benchmarks.SmallPayload{} 67 | gojay.Unsafe.UnmarshalJSONObject(benchmarks.SmallFixture, &result) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /benchmarks/decoder/decoder_large_test.go: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/francoispqt/gojay" 7 | "github.com/francoispqt/gojay/benchmarks" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestGoJayDecodeObjLarge(t *testing.T) { 12 | result := benchmarks.LargePayload{} 13 | err := gojay.UnmarshalJSONObject(benchmarks.LargeFixture, &result) 14 | assert.Nil(t, err, "err should be nil") 15 | assert.Len(t, result.Users, 32, "Len of users should be 32") 16 | for _, u := range result.Users { 17 | assert.True(t, len(u.Username) > 0, "User should have username") 18 | } 19 | assert.Len(t, result.Topics.Topics, 30, "Len of topics should be 30") 20 | for _, top := range result.Topics.Topics { 21 | assert.True(t, top.Id > 0, "Topic should have Id") 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /benchmarks/decoder/decoder_medium_test.go: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/francoispqt/gojay" 7 | "github.com/francoispqt/gojay/benchmarks" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestGoJayDecodeObjMedium(t *testing.T) { 12 | result := benchmarks.MediumPayload{} 13 | err := gojay.Unmarshal(benchmarks.MediumFixture, &result) 14 | assert.Nil(t, err, "err should be nil") 15 | assert.Equal(t, "Leonid Bugaev", result.Person.Name.FullName, "result.Person.Name.FullName should be Leonid Bugaev") 16 | assert.Equal(t, 95, result.Person.Github.Followers, "result.Person.Github.Followers should be 95") 17 | assert.Len(t, result.Person.Gravatar.Avatars, 1, "result.Person.Gravatar.Avatars should have 1 item") 18 | } 19 | -------------------------------------------------------------------------------- /benchmarks/decoder/decoder_small_test.go: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/francoispqt/gojay" 7 | "github.com/francoispqt/gojay/benchmarks" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestGoJayDecodeObjSmall(t *testing.T) { 12 | result := benchmarks.SmallPayload{} 13 | err := gojay.Unmarshal(benchmarks.SmallFixture, &result) 14 | assert.Nil(t, err, "err should be nil") 15 | assert.Equal(t, result.St, 1, "result.St should be 1") 16 | assert.Equal(t, result.Sid, 486, "result.Sid should be 486") 17 | assert.Equal(t, result.Tt, "active", "result.Sid should be 'active'") 18 | assert.Equal(t, result.Gr, 0, "result.Gr should be 0") 19 | assert.Equal( 20 | t, 21 | result.Uuid, 22 | "de305d54-75b4-431b-adb2-eb6b9e546014", 23 | "result.Gr should be 'de305d54-75b4-431b-adb2-eb6b9e546014'", 24 | ) 25 | assert.Equal(t, result.Ip, "127.0.0.1", "result.Ip should be '127.0.0.1'") 26 | assert.Equal(t, result.Ua, "user_agent", "result.Ua should be 'user_agent'") 27 | assert.Equal(t, result.Tz, -6, "result.Tz should be 6") 28 | assert.Equal(t, result.V, 1, "result.V should be 1") 29 | } 30 | -------------------------------------------------------------------------------- /benchmarks/encoder/.gitignore: -------------------------------------------------------------------------------- 1 | vendor/* -------------------------------------------------------------------------------- /benchmarks/encoder/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: bench 2 | bench: 3 | go test -benchmem -run=^$ github.com/francoispqt/gojay/benchmarks/encoder -bench ^Benchmark 4 | 5 | .PHONY: benchtrace 6 | benchtrace: 7 | go test -c & GODEBUG=allocfreetrace=1 ./encoder.test -test.run=none -test.bench=^Benchmark -test.benchtime=10ms 2>trace.log 8 | 9 | .PHONY: benchcpu 10 | benchcpu: 11 | go test -benchmem -run=^$ github.com/francoispqt/gojay/benchmarks/encoder -bench ^Benchmark -cpuprofile cpu.out 12 | 13 | .PHONY: testtrace 14 | testtrace: 15 | go test -benchmem -run=^$ github.com/francoispqt/gojay/benchmarks/encoder -bench ^Benchmark -trace trace.out 16 | 17 | .PHONY: benchgojay 18 | benchgojay: 19 | go test -benchmem -run=^BenchmarkGoJay -bench=^BenchmarkGoJay -benchtime=30ms 20 | 21 | .PHONY: benchgojaycpu 22 | benchgojaycpu: 23 | go test -benchmem -run=^BenchmarkGoJay -bench=^BenchmarkGoJay -benchtime=30ms -cpuprofile cpu.out 24 | 25 | .PHONY: benchjsoniter 26 | benchjsoniter: 27 | go test -benchmem -run=^BenchmarkJsonIter -bench=^BenchmarkJsonIter -benchtime=30ms 28 | 29 | .PHONY: benchjsonparser 30 | benchjsonparser: 31 | go test -benchmem -run=^BenchmarkJsonParser -bench=^BenchmarkJsonParser -benchtime=30ms -------------------------------------------------------------------------------- /benchmarks/encoder/encoder_bench_large_test.go: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "testing" 7 | 8 | "github.com/francoispqt/gojay" 9 | "github.com/francoispqt/gojay/benchmarks" 10 | jsoniter "github.com/json-iterator/go" 11 | "github.com/mailru/easyjson" 12 | ) 13 | 14 | func BenchmarkEncodingJsonEncodeLargeStruct(b *testing.B) { 15 | b.ReportAllocs() 16 | for i := 0; i < b.N; i++ { 17 | if _, err := json.Marshal(benchmarks.NewLargePayload()); err != nil { 18 | b.Fatal(err) 19 | } 20 | } 21 | } 22 | func BenchmarkJsonIterEncodeLargeStruct(b *testing.B) { 23 | var json = jsoniter.ConfigCompatibleWithStandardLibrary 24 | b.ReportAllocs() 25 | for i := 0; i < b.N; i++ { 26 | if _, err := json.Marshal(benchmarks.NewLargePayload()); err != nil { 27 | b.Fatal(err) 28 | } 29 | } 30 | } 31 | 32 | func BenchmarkEasyJsonEncodeObjLarge(b *testing.B) { 33 | b.ReportAllocs() 34 | for i := 0; i < b.N; i++ { 35 | if _, err := easyjson.Marshal(benchmarks.NewLargePayload()); err != nil { 36 | b.Fatal(err) 37 | } 38 | } 39 | } 40 | 41 | func BenchmarkGoJayEncodeLargeStruct(b *testing.B) { 42 | b.ReportAllocs() 43 | for i := 0; i < b.N; i++ { 44 | if _, err := gojay.MarshalJSONObject(benchmarks.NewLargePayload()); err != nil { 45 | b.Fatal(err) 46 | } 47 | } 48 | } 49 | 50 | func TestGoJayEncodeLargeStruct(t *testing.T) { 51 | if output, err := gojay.MarshalJSONObject(benchmarks.NewLargePayload()); err != nil { 52 | t.Fatal(err) 53 | } else { 54 | log.Print(string(output)) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /benchmarks/encoder/encoder_bench_medium_test.go: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "testing" 7 | 8 | "github.com/francoispqt/gojay" 9 | "github.com/francoispqt/gojay/benchmarks" 10 | jsoniter "github.com/json-iterator/go" 11 | "github.com/mailru/easyjson" 12 | ) 13 | 14 | func BenchmarkEncodingJsonEncodeMediumStruct(b *testing.B) { 15 | b.ReportAllocs() 16 | for i := 0; i < b.N; i++ { 17 | if _, err := json.Marshal(benchmarks.NewMediumPayload()); err != nil { 18 | b.Fatal(err) 19 | } 20 | } 21 | } 22 | 23 | func BenchmarkJsonIterEncodeMediumStruct(b *testing.B) { 24 | var json = jsoniter.ConfigCompatibleWithStandardLibrary 25 | b.ReportAllocs() 26 | for i := 0; i < b.N; i++ { 27 | if _, err := json.Marshal(benchmarks.NewMediumPayload()); err != nil { 28 | b.Fatal(err) 29 | } 30 | } 31 | } 32 | 33 | func BenchmarkEasyJsonEncodeObjMedium(b *testing.B) { 34 | b.ReportAllocs() 35 | for i := 0; i < b.N; i++ { 36 | if _, err := easyjson.Marshal(benchmarks.NewMediumPayload()); err != nil { 37 | b.Fatal(err) 38 | } 39 | } 40 | } 41 | 42 | func BenchmarkGoJayEncodeMediumStruct(b *testing.B) { 43 | b.ReportAllocs() 44 | for i := 0; i < b.N; i++ { 45 | if _, err := gojay.MarshalJSONObject(benchmarks.NewMediumPayload()); err != nil { 46 | b.Fatal(err) 47 | } 48 | } 49 | } 50 | 51 | func TestGoJayEncodeMediumStruct(t *testing.T) { 52 | if output, err := gojay.MarshalJSONObject(benchmarks.NewMediumPayload()); err != nil { 53 | t.Fatal(err) 54 | } else { 55 | log.Print(string(output)) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /benchmarks/encoder/encoder_bench_small_test.go: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "testing" 7 | 8 | "github.com/francoispqt/gojay" 9 | "github.com/francoispqt/gojay/benchmarks" 10 | jsoniter "github.com/json-iterator/go" 11 | "github.com/mailru/easyjson" 12 | ) 13 | 14 | func BenchmarkEncodingJsonEncodeSmallStruct(b *testing.B) { 15 | b.ReportAllocs() 16 | for i := 0; i < b.N; i++ { 17 | if _, err := json.Marshal(benchmarks.NewSmallPayload()); err != nil { 18 | b.Fatal(err) 19 | } 20 | } 21 | } 22 | 23 | func BenchmarkEasyJsonEncodeObjSmall(b *testing.B) { 24 | b.ReportAllocs() 25 | for i := 0; i < b.N; i++ { 26 | if _, err := easyjson.Marshal(benchmarks.NewSmallPayload()); err != nil { 27 | b.Fatal(err) 28 | } 29 | } 30 | } 31 | 32 | func BenchmarkJsonIterEncodeSmallStruct(b *testing.B) { 33 | var json = jsoniter.ConfigCompatibleWithStandardLibrary 34 | b.ReportAllocs() 35 | for i := 0; i < b.N; i++ { 36 | if _, err := json.Marshal(benchmarks.NewSmallPayload()); err != nil { 37 | b.Fatal(err) 38 | } 39 | } 40 | } 41 | 42 | func BenchmarkGoJayEncodeSmallStruct(b *testing.B) { 43 | b.ReportAllocs() 44 | for i := 0; i < b.N; i++ { 45 | if _, err := gojay.MarshalJSONObject(benchmarks.NewSmallPayload()); err != nil { 46 | b.Fatal(err) 47 | } 48 | } 49 | } 50 | 51 | func BenchmarkGoJayEncodeSmallFunc(b *testing.B) { 52 | b.ReportAllocs() 53 | for i := 0; i < b.N; i++ { 54 | if _, err := gojay.MarshalJSONObject(gojay.EncodeObjectFunc(func(enc *gojay.Encoder) { 55 | enc.AddIntKey("st", 1) 56 | enc.AddIntKey("sid", 1) 57 | enc.AddStringKey("tt", "test") 58 | enc.AddIntKey("gr", 1) 59 | enc.AddStringKey("uuid", "test") 60 | enc.AddStringKey("ip", "test") 61 | enc.AddStringKey("ua", "test") 62 | enc.AddIntKey("tz", 1) 63 | enc.AddIntKey("v", 1) 64 | })); err != nil { 65 | b.Fatal(err) 66 | } 67 | } 68 | } 69 | 70 | func TestGoJayEncodeSmallStruct(t *testing.T) { 71 | if output, err := gojay.MarshalJSONObject(benchmarks.NewSmallPayload()); err != nil { 72 | t.Fatal(err) 73 | } else { 74 | log.Print(output) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /benchmarks/encoder/encoder_large_test.go: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | -------------------------------------------------------------------------------- /benchmarks/encoder/encoder_medium_test.go: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | -------------------------------------------------------------------------------- /benchmarks/encoder/encoder_small_test.go: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | -------------------------------------------------------------------------------- /decode_embedded_json.go: -------------------------------------------------------------------------------- 1 | package gojay 2 | 3 | // EmbeddedJSON is a raw encoded JSON value. 4 | // It can be used to delay JSON decoding or precompute a JSON encoding. 5 | type EmbeddedJSON []byte 6 | 7 | func (dec *Decoder) decodeEmbeddedJSON(ej *EmbeddedJSON) error { 8 | var err error 9 | if ej == nil { 10 | return InvalidUnmarshalError("Invalid nil pointer given") 11 | } 12 | var beginOfEmbeddedJSON int 13 | for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { 14 | switch dec.data[dec.cursor] { 15 | case ' ', '\n', '\t', '\r', ',': 16 | continue 17 | // is null 18 | case 'n': 19 | beginOfEmbeddedJSON = dec.cursor 20 | dec.cursor++ 21 | err := dec.assertNull() 22 | if err != nil { 23 | return err 24 | } 25 | case 't': 26 | beginOfEmbeddedJSON = dec.cursor 27 | dec.cursor++ 28 | err := dec.assertTrue() 29 | if err != nil { 30 | return err 31 | } 32 | // is false 33 | case 'f': 34 | beginOfEmbeddedJSON = dec.cursor 35 | dec.cursor++ 36 | err := dec.assertFalse() 37 | if err != nil { 38 | return err 39 | } 40 | // is an object 41 | case '{': 42 | beginOfEmbeddedJSON = dec.cursor 43 | dec.cursor = dec.cursor + 1 44 | dec.cursor, err = dec.skipObject() 45 | // is string 46 | case '"': 47 | beginOfEmbeddedJSON = dec.cursor 48 | dec.cursor = dec.cursor + 1 49 | err = dec.skipString() // why no new dec.cursor in result? 50 | // is array 51 | case '[': 52 | beginOfEmbeddedJSON = dec.cursor 53 | dec.cursor = dec.cursor + 1 54 | dec.cursor, err = dec.skipArray() 55 | case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-': 56 | beginOfEmbeddedJSON = dec.cursor 57 | dec.cursor, err = dec.skipNumber() 58 | } 59 | break 60 | } 61 | if err == nil { 62 | if dec.cursor-1 >= beginOfEmbeddedJSON { 63 | *ej = append(*ej, dec.data[beginOfEmbeddedJSON:dec.cursor]...) 64 | } 65 | dec.called |= 1 66 | } 67 | return err 68 | } 69 | 70 | // AddEmbeddedJSON adds an EmbeddedsJSON to the value pointed by v. 71 | // It can be used to delay JSON decoding or precompute a JSON encoding. 72 | func (dec *Decoder) AddEmbeddedJSON(v *EmbeddedJSON) error { 73 | return dec.EmbeddedJSON(v) 74 | } 75 | 76 | // EmbeddedJSON adds an EmbeddedsJSON to the value pointed by v. 77 | // It can be used to delay JSON decoding or precompute a JSON encoding. 78 | func (dec *Decoder) EmbeddedJSON(v *EmbeddedJSON) error { 79 | err := dec.decodeEmbeddedJSON(v) 80 | if err != nil { 81 | return err 82 | } 83 | dec.called |= 1 84 | return nil 85 | } 86 | -------------------------------------------------------------------------------- /decode_embedded_json_test.go: -------------------------------------------------------------------------------- 1 | package gojay 2 | 3 | import ( 4 | "bytes" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | type Request struct { 12 | id string 13 | method string 14 | params EmbeddedJSON 15 | more int 16 | } 17 | 18 | func (r *Request) UnmarshalJSONObject(dec *Decoder, key string) error { 19 | switch key { 20 | case "id": 21 | return dec.AddString(&r.id) 22 | case "method": 23 | return dec.AddString(&r.method) 24 | case "params": 25 | return dec.AddEmbeddedJSON(&r.params) 26 | case "more": 27 | dec.AddInt(&r.more) 28 | } 29 | return nil 30 | } 31 | 32 | func (r *Request) NKeys() int { 33 | return 4 34 | } 35 | 36 | func TestDecodeEmbeddedJSONUnmarshalAPI(t *testing.T) { 37 | testCases := []struct { 38 | name string 39 | json []byte 40 | expectedEmbedded string 41 | err bool 42 | }{ 43 | { 44 | name: "decode-basic-string", 45 | json: []byte(`{"id":"someid","method":"getmydata","params":"raw data", "more":123}`), 46 | expectedEmbedded: `"raw data"`, 47 | }, 48 | { 49 | name: "decode-basic-int", 50 | json: []byte(`{"id":"someid","method":"getmydata","params":12345, "more":123}`), 51 | expectedEmbedded: `12345`, 52 | }, 53 | { 54 | name: "decode-basic-int", 55 | json: []byte(`{"id":"someid","method":"getmydata","params":true, "more":123}`), 56 | expectedEmbedded: `true`, 57 | }, 58 | { 59 | name: "decode-basic-int", 60 | json: []byte(`{"id":"someid","method":"getmydata","params": false, "more":123}`), 61 | expectedEmbedded: `false`, 62 | }, 63 | { 64 | name: "decode-basic-int", 65 | json: []byte(`{"id":"someid","method":"getmydata","params":null, "more":123}`), 66 | expectedEmbedded: `null`, 67 | }, 68 | { 69 | name: "decode-basic-object", 70 | json: []byte(`{"id":"someid","method":"getmydata","params":{"example":"of raw data"}, "more":123}`), 71 | expectedEmbedded: `{"example":"of raw data"}`, 72 | }, 73 | { 74 | name: "decode-basic-object", 75 | json: []byte(`{"id":"someid","method":"getmydata","params":[1,2,3], "more":123}`), 76 | expectedEmbedded: `[1,2,3]`, 77 | }, 78 | { 79 | name: "decode-null-err", 80 | json: []byte(`{"id":"someid","method":"getmydata","params":nil, "more":123}`), 81 | expectedEmbedded: ``, 82 | err: true, 83 | }, 84 | { 85 | name: "decode-bool-false-err", 86 | json: []byte(`{"id":"someid","method":"getmydata","params":faulse, "more":123}`), 87 | expectedEmbedded: ``, 88 | err: true, 89 | }, 90 | { 91 | name: "decode-bool-true-err", 92 | json: []byte(`{"id":"someid","method":"getmydata","params":trou, "more":123}`), 93 | expectedEmbedded: ``, 94 | err: true, 95 | }, 96 | } 97 | for _, testCase := range testCases { 98 | t.Run(testCase.name, func(t *testing.T) { 99 | req := &Request{} 100 | err := Unmarshal(testCase.json, req) 101 | t.Log(req) 102 | t.Log(string(req.params)) 103 | if testCase.err { 104 | assert.NotNil(t, err, "err should not be nil") 105 | } else { 106 | assert.Nil(t, err, "err should be nil") 107 | } 108 | assert.Equal(t, testCase.expectedEmbedded, string(req.params), "r.params should be equal to expectedEmbeddedResult") 109 | }) 110 | } 111 | } 112 | 113 | func TestDecodeEmbeddedJSONDecodeAPI(t *testing.T) { 114 | testCases := []struct { 115 | name string 116 | json []byte 117 | expectedEmbedded string 118 | }{ 119 | { 120 | name: "decode-basic-string", 121 | json: []byte(`{"id":"someid","method":"getmydata","params":"raw data", "more":123}`), 122 | expectedEmbedded: `"raw data"`, 123 | }, 124 | { 125 | name: "decode-basic-int", 126 | json: []byte(`{"id":"someid","method":"getmydata","params":12345, "more":123}`), 127 | expectedEmbedded: `12345`, 128 | }, 129 | { 130 | name: "decode-basic-int", 131 | json: []byte(`{"id":"someid","method":"getmydata","params":true, "more":123}`), 132 | expectedEmbedded: `true`, 133 | }, 134 | { 135 | name: "decode-basic-int", 136 | json: []byte(`{"id":"someid","method":"getmydata","params": false, "more":123}`), 137 | expectedEmbedded: `false`, 138 | }, 139 | { 140 | name: "decode-basic-int", 141 | json: []byte(`{"id":"someid","method":"getmydata","params":null, "more":123}`), 142 | expectedEmbedded: `null`, 143 | }, 144 | { 145 | name: "decode-basic-object", 146 | json: []byte(`{"id":"someid","method":"getmydata","params":{"example":"of raw data"}, "more":123}`), 147 | expectedEmbedded: `{"example":"of raw data"}`, 148 | }, 149 | { 150 | name: "decode-basic-object", 151 | json: []byte(`{"id":"someid","method":"getmydata","params":[1,2,3], "more":123}`), 152 | expectedEmbedded: `[1,2,3]`, 153 | }, 154 | } 155 | for _, testCase := range testCases { 156 | t.Run(testCase.name, func(t *testing.T) { 157 | ej := EmbeddedJSON([]byte{}) 158 | dec := BorrowDecoder(bytes.NewReader(testCase.json)) 159 | err := dec.Decode(&ej) 160 | assert.Nil(t, err, "err should be nil") 161 | assert.Equal(t, string(testCase.json), string(ej), "r.params should be equal to expectedEmbeddedResult") 162 | }) 163 | } 164 | } 165 | 166 | func TestDecodeEmbeededJSONNil(t *testing.T) { 167 | dec := BorrowDecoder(strings.NewReader(`"bar"`)) 168 | var ej *EmbeddedJSON 169 | err := dec.decodeEmbeddedJSON(ej) 170 | assert.NotNil(t, err, `err should not be nil a nil pointer is given`) 171 | assert.IsType(t, InvalidUnmarshalError(""), err, `err should not be of type InvalidUnmarshalError`) 172 | } 173 | 174 | func TestDecodeEmbeededJSONNil2(t *testing.T) { 175 | dec := BorrowDecoder(strings.NewReader(`"bar"`)) 176 | var ej *EmbeddedJSON 177 | err := dec.AddEmbeddedJSON(ej) 178 | assert.NotNil(t, err, `err should not be nil a nil pointer is given`) 179 | assert.IsType(t, InvalidUnmarshalError(""), err, `err should not be of type InvalidUnmarshalError`) 180 | } 181 | -------------------------------------------------------------------------------- /decode_example_test.go: -------------------------------------------------------------------------------- 1 | package gojay_test 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "strings" 7 | 8 | "github.com/francoispqt/gojay" 9 | ) 10 | 11 | func ExampleUnmarshal_string() { 12 | data := []byte(`"gojay"`) 13 | var str string 14 | err := gojay.Unmarshal(data, &str) 15 | if err != nil { 16 | log.Fatal(err) 17 | } 18 | fmt.Println(str) // true 19 | } 20 | 21 | func ExampleUnmarshal_bool() { 22 | data := []byte(`true`) 23 | var b bool 24 | err := gojay.Unmarshal(data, &b) 25 | if err != nil { 26 | log.Fatal(err) 27 | } 28 | fmt.Println(b) // true 29 | } 30 | 31 | func ExampleUnmarshal_invalidType() { 32 | data := []byte(`"gojay"`) 33 | someStruct := struct{}{} 34 | err := gojay.Unmarshal(data, &someStruct) 35 | 36 | fmt.Println(err) // "Cannot unmarshal JSON to type '*struct{}'" 37 | } 38 | 39 | func ExampleDecoder_Decode_string() { 40 | var str string 41 | dec := gojay.BorrowDecoder(strings.NewReader(`"gojay"`)) 42 | err := dec.Decode(&str) 43 | dec.Release() 44 | 45 | if err != nil { 46 | log.Fatal(err) 47 | } 48 | fmt.Println(str) // "gojay" 49 | } 50 | 51 | func ExampleDecodeObjectFunc() { 52 | reader := strings.NewReader(`{ 53 | "name": "John Doe", 54 | "email": "john.doe@email.com" 55 | }`) 56 | dec := gojay.NewDecoder(reader) 57 | 58 | user := struct { 59 | name string 60 | email string 61 | }{} 62 | dec.DecodeObject(gojay.DecodeObjectFunc(func(dec *gojay.Decoder, k string) error { 63 | switch k { 64 | case "name": 65 | return dec.String(&user.name) 66 | case "email": 67 | return dec.String(&user.email) 68 | } 69 | return nil 70 | })) 71 | 72 | fmt.Printf("User\nname: %s\nemail: %s", user.name, user.email) 73 | 74 | // Output: 75 | // User 76 | // name: John Doe 77 | // email: john.doe@email.com 78 | } 79 | 80 | func ExampleDecodeArrayFunc() { 81 | reader := strings.NewReader(`[ 82 | "foo", 83 | "bar" 84 | ]`) 85 | dec := gojay.NewDecoder(reader) 86 | 87 | strSlice := make([]string, 0) 88 | err := dec.DecodeArray(gojay.DecodeArrayFunc(func(dec *gojay.Decoder) error { 89 | var str string 90 | if err := dec.AddString(&str); err != nil { 91 | return err 92 | } 93 | strSlice = append(strSlice, str) 94 | return nil 95 | })) 96 | 97 | if err != nil { 98 | log.Fatal(err) 99 | } 100 | 101 | fmt.Print(strSlice) 102 | // Output: 103 | // [foo bar] 104 | } 105 | 106 | func ExampleNewDecoder() { 107 | reader := strings.NewReader(`"gojay"`) 108 | dec := gojay.NewDecoder(reader) 109 | 110 | var str string 111 | err := dec.DecodeString(&str) 112 | if err != nil { 113 | log.Fatal(err) 114 | } 115 | 116 | fmt.Println(str) 117 | // Output: 118 | // gojay 119 | } 120 | 121 | func ExampleBorrowDecoder() { 122 | reader := strings.NewReader(`"gojay"`) 123 | dec := gojay.BorrowDecoder(reader) 124 | defer dec.Release() 125 | 126 | var str string 127 | err := dec.DecodeString(&str) 128 | if err != nil { 129 | log.Fatal(err) 130 | } 131 | 132 | fmt.Println(str) 133 | // Output: 134 | // gojay 135 | } 136 | 137 | func ExampleDecoder_DecodeBool() { 138 | reader := strings.NewReader(`true`) 139 | dec := gojay.NewDecoder(reader) 140 | 141 | var b bool 142 | err := dec.DecodeBool(&b) 143 | if err != nil { 144 | log.Fatal(err) 145 | } 146 | 147 | fmt.Println(b) 148 | // Output: 149 | // true 150 | } 151 | -------------------------------------------------------------------------------- /decode_interface.go: -------------------------------------------------------------------------------- 1 | package gojay 2 | 3 | // TODO @afiune for now we are using the standard json unmarshaling but in 4 | // the future it would be great to implement one here inside this repo 5 | import "encoding/json" 6 | 7 | // DecodeInterface reads the next JSON-encoded value from the decoder's input (io.Reader) and stores it in the value pointed to by i. 8 | // 9 | // i must be an interface poiter 10 | func (dec *Decoder) DecodeInterface(i *interface{}) error { 11 | if dec.isPooled == 1 { 12 | panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) 13 | } 14 | err := dec.decodeInterface(i) 15 | return err 16 | } 17 | 18 | func (dec *Decoder) decodeInterface(i *interface{}) error { 19 | start, end, err := dec.getObject() 20 | if err != nil { 21 | dec.cursor = start 22 | return err 23 | } 24 | 25 | // if start & end are equal the object is a null, don't unmarshal 26 | if start == end { 27 | return nil 28 | } 29 | 30 | object := dec.data[start:end] 31 | if err = json.Unmarshal(object, i); err != nil { 32 | return err 33 | } 34 | 35 | dec.cursor = end 36 | return nil 37 | } 38 | 39 | // @afiune Maybe return the type as well? 40 | func (dec *Decoder) getObject() (start int, end int, err error) { 41 | // start cursor 42 | for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { 43 | switch dec.data[dec.cursor] { 44 | case ' ', '\n', '\t', '\r', ',': 45 | continue 46 | // is null 47 | case 'n': 48 | dec.cursor++ 49 | err = dec.assertNull() 50 | if err != nil { 51 | return 52 | } 53 | // Set start & end to the same cursor to indicate the object 54 | // is a null and should not be unmarshal 55 | start = dec.cursor 56 | end = dec.cursor 57 | return 58 | case 't': 59 | start = dec.cursor 60 | dec.cursor++ 61 | err = dec.assertTrue() 62 | if err != nil { 63 | return 64 | } 65 | end = dec.cursor 66 | dec.cursor++ 67 | return 68 | // is false 69 | case 'f': 70 | start = dec.cursor 71 | dec.cursor++ 72 | err = dec.assertFalse() 73 | if err != nil { 74 | return 75 | } 76 | end = dec.cursor 77 | dec.cursor++ 78 | return 79 | // is an object 80 | case '{': 81 | start = dec.cursor 82 | dec.cursor++ 83 | end, err = dec.skipObject() 84 | dec.cursor = end 85 | return 86 | // is string 87 | case '"': 88 | start = dec.cursor 89 | dec.cursor++ 90 | start, end, err = dec.getString() 91 | start-- 92 | dec.cursor = end 93 | return 94 | // is array 95 | case '[': 96 | start = dec.cursor 97 | dec.cursor++ 98 | end, err = dec.skipArray() 99 | dec.cursor = end 100 | return 101 | case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-': 102 | start = dec.cursor 103 | end, err = dec.skipNumber() 104 | dec.cursor = end 105 | return 106 | default: 107 | err = dec.raiseInvalidJSONErr(dec.cursor) 108 | return 109 | } 110 | } 111 | err = dec.raiseInvalidJSONErr(dec.cursor) 112 | return 113 | } 114 | 115 | // Add Values functions 116 | 117 | // AddInterface decodes the JSON value within an object or an array to a interface{}. 118 | func (dec *Decoder) AddInterface(v *interface{}) error { 119 | return dec.Interface(v) 120 | } 121 | 122 | // Interface decodes the JSON value within an object or an array to an interface{}. 123 | func (dec *Decoder) Interface(value *interface{}) error { 124 | err := dec.decodeInterface(value) 125 | if err != nil { 126 | return err 127 | } 128 | dec.called |= 1 129 | return nil 130 | } 131 | -------------------------------------------------------------------------------- /decode_number.go: -------------------------------------------------------------------------------- 1 | package gojay 2 | 3 | import ( 4 | "math" 5 | ) 6 | 7 | var digits []int8 8 | 9 | const maxInt64toMultiply = math.MaxInt64 / 10 10 | const maxInt32toMultiply = math.MaxInt32 / 10 11 | const maxInt16toMultiply = math.MaxInt16 / 10 12 | const maxInt8toMultiply = math.MaxInt8 / 10 13 | const maxUint8toMultiply = math.MaxUint8 / 10 14 | const maxUint16toMultiply = math.MaxUint16 / 10 15 | const maxUint32toMultiply = math.MaxUint32 / 10 16 | const maxUint64toMultiply = math.MaxUint64 / 10 17 | const maxUint32Length = 10 18 | const maxUint64Length = 20 19 | const maxUint16Length = 5 20 | const maxUint8Length = 3 21 | const maxInt32Length = 10 22 | const maxInt64Length = 19 23 | const maxInt16Length = 5 24 | const maxInt8Length = 3 25 | const invalidNumber = int8(-1) 26 | 27 | var pow10uint64 = [21]uint64{ 28 | 0, 29 | 1, 30 | 10, 31 | 100, 32 | 1000, 33 | 10000, 34 | 100000, 35 | 1000000, 36 | 10000000, 37 | 100000000, 38 | 1000000000, 39 | 10000000000, 40 | 100000000000, 41 | 1000000000000, 42 | 10000000000000, 43 | 100000000000000, 44 | 1000000000000000, 45 | 10000000000000000, 46 | 100000000000000000, 47 | 1000000000000000000, 48 | 10000000000000000000, 49 | } 50 | 51 | var skipNumberEndCursorIncrement [256]int 52 | 53 | func init() { 54 | digits = make([]int8, 256) 55 | for i := 0; i < len(digits); i++ { 56 | digits[i] = invalidNumber 57 | } 58 | for i := int8('0'); i <= int8('9'); i++ { 59 | digits[i] = i - int8('0') 60 | } 61 | 62 | for i := 0; i < 256; i++ { 63 | switch i { 64 | case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'e', 'E', '+', '-': 65 | skipNumberEndCursorIncrement[i] = 1 66 | } 67 | } 68 | } 69 | 70 | func (dec *Decoder) skipNumber() (int, error) { 71 | end := dec.cursor + 1 72 | // look for following numbers 73 | for j := dec.cursor + 1; j < dec.length || dec.read(); j++ { 74 | end += skipNumberEndCursorIncrement[dec.data[j]] 75 | 76 | switch dec.data[j] { 77 | case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'e', 'E', '+', '-', ' ', '\n', '\t', '\r': 78 | continue 79 | case ',', '}', ']': 80 | return end, nil 81 | default: 82 | // invalid json we expect numbers, dot (single one), comma, or spaces 83 | return end, dec.raiseInvalidJSONErr(dec.cursor) 84 | } 85 | } 86 | 87 | return end, nil 88 | } 89 | 90 | func (dec *Decoder) getExponent() (int64, error) { 91 | start := dec.cursor 92 | end := dec.cursor 93 | for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { 94 | switch dec.data[dec.cursor] { // is positive 95 | case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': 96 | end = dec.cursor + 1 97 | case '-': 98 | dec.cursor++ 99 | exp, err := dec.getExponent() 100 | return -exp, err 101 | case '+': 102 | dec.cursor++ 103 | return dec.getExponent() 104 | default: 105 | // if nothing return 0 106 | // could raise error 107 | if start == end { 108 | return 0, dec.raiseInvalidJSONErr(dec.cursor) 109 | } 110 | return dec.atoi64(start, end-1), nil 111 | } 112 | } 113 | if start == end { 114 | 115 | return 0, dec.raiseInvalidJSONErr(dec.cursor) 116 | } 117 | return dec.atoi64(start, end-1), nil 118 | } 119 | -------------------------------------------------------------------------------- /decode_number_test.go: -------------------------------------------------------------------------------- 1 | package gojay 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestDecodeNumberExra(t *testing.T) { 11 | t.Run("skip-number-err", func(t *testing.T) { 12 | dec := NewDecoder(strings.NewReader("123456afzfz343")) 13 | _, err := dec.skipNumber() 14 | assert.NotNil(t, err, "err should not be nil") 15 | assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") 16 | }) 17 | t.Run("get-exponent-err", func(t *testing.T) { 18 | v := 0 19 | dec := NewDecoder(strings.NewReader("1.2Ea")) 20 | err := dec.Decode(&v) 21 | assert.NotNil(t, err, "err should not be nil") 22 | assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /decode_pool.go: -------------------------------------------------------------------------------- 1 | package gojay 2 | 3 | import ( 4 | "io" 5 | "sync" 6 | ) 7 | 8 | var decPool = sync.Pool{ 9 | New: newDecoderPool, 10 | } 11 | 12 | func init() { 13 | for i := 0; i < 32; i++ { 14 | decPool.Put(NewDecoder(nil)) 15 | } 16 | } 17 | 18 | // NewDecoder returns a new decoder. 19 | // It takes an io.Reader implementation as data input. 20 | func NewDecoder(r io.Reader) *Decoder { 21 | return &Decoder{ 22 | called: 0, 23 | cursor: 0, 24 | keysDone: 0, 25 | err: nil, 26 | r: r, 27 | data: make([]byte, 512), 28 | length: 0, 29 | isPooled: 0, 30 | } 31 | } 32 | func newDecoderPool() interface{} { 33 | return NewDecoder(nil) 34 | } 35 | 36 | // BorrowDecoder borrows a Decoder from the pool. 37 | // It takes an io.Reader implementation as data input. 38 | // 39 | // In order to benefit from the pool, a borrowed decoder must be released after usage. 40 | func BorrowDecoder(r io.Reader) *Decoder { 41 | return borrowDecoder(r, 512) 42 | } 43 | func borrowDecoder(r io.Reader, bufSize int) *Decoder { 44 | dec := decPool.Get().(*Decoder) 45 | dec.called = 0 46 | dec.keysDone = 0 47 | dec.cursor = 0 48 | dec.err = nil 49 | dec.r = r 50 | dec.length = 0 51 | dec.isPooled = 0 52 | if bufSize > 0 { 53 | dec.data = make([]byte, bufSize) 54 | } 55 | return dec 56 | } 57 | 58 | // Release sends back a Decoder to the pool. 59 | // If a decoder is used after calling Release 60 | // a panic will be raised with an InvalidUsagePooledDecoderError error. 61 | func (dec *Decoder) Release() { 62 | dec.isPooled = 1 63 | decPool.Put(dec) 64 | } 65 | -------------------------------------------------------------------------------- /decode_pool_test.go: -------------------------------------------------------------------------------- 1 | package gojay 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestDecoderBorrowFromPoolSetBuffSize(t *testing.T) { 10 | dec := borrowDecoder(nil, 512) 11 | assert.Len(t, dec.data, 512, "data buffer should be of len 512") 12 | } 13 | 14 | func TestDecoderNewPool(t *testing.T) { 15 | dec := newDecoderPool() 16 | assert.IsType(t, &Decoder{}, dec, "dec should be a *Decoder") 17 | } 18 | -------------------------------------------------------------------------------- /decode_slice.go: -------------------------------------------------------------------------------- 1 | package gojay 2 | 3 | // AddSliceString unmarshals the next JSON array of strings to the given *[]string s 4 | func (dec *Decoder) AddSliceString(s *[]string) error { 5 | return dec.SliceString(s) 6 | } 7 | 8 | // SliceString unmarshals the next JSON array of strings to the given *[]string s 9 | func (dec *Decoder) SliceString(s *[]string) error { 10 | err := dec.Array(DecodeArrayFunc(func(dec *Decoder) error { 11 | var str string 12 | if err := dec.String(&str); err != nil { 13 | return err 14 | } 15 | *s = append(*s, str) 16 | return nil 17 | })) 18 | 19 | if err != nil { 20 | return err 21 | } 22 | return nil 23 | } 24 | 25 | // AddSliceInt unmarshals the next JSON array of integers to the given *[]int s 26 | func (dec *Decoder) AddSliceInt(s *[]int) error { 27 | return dec.SliceInt(s) 28 | } 29 | 30 | // SliceInt unmarshals the next JSON array of integers to the given *[]int s 31 | func (dec *Decoder) SliceInt(s *[]int) error { 32 | err := dec.Array(DecodeArrayFunc(func(dec *Decoder) error { 33 | var i int 34 | if err := dec.Int(&i); err != nil { 35 | return err 36 | } 37 | *s = append(*s, i) 38 | return nil 39 | })) 40 | 41 | if err != nil { 42 | return err 43 | } 44 | return nil 45 | } 46 | 47 | // AddFloat64 unmarshals the next JSON array of floats to the given *[]float64 s 48 | func (dec *Decoder) AddSliceFloat64(s *[]float64) error { 49 | return dec.SliceFloat64(s) 50 | } 51 | 52 | // SliceFloat64 unmarshals the next JSON array of floats to the given *[]float64 s 53 | func (dec *Decoder) SliceFloat64(s *[]float64) error { 54 | err := dec.Array(DecodeArrayFunc(func(dec *Decoder) error { 55 | var i float64 56 | if err := dec.Float64(&i); err != nil { 57 | return err 58 | } 59 | *s = append(*s, i) 60 | return nil 61 | })) 62 | 63 | if err != nil { 64 | return err 65 | } 66 | return nil 67 | } 68 | 69 | // AddBool unmarshals the next JSON array of boolegers to the given *[]bool s 70 | func (dec *Decoder) AddSliceBool(s *[]bool) error { 71 | return dec.SliceBool(s) 72 | } 73 | 74 | // SliceBool unmarshals the next JSON array of boolegers to the given *[]bool s 75 | func (dec *Decoder) SliceBool(s *[]bool) error { 76 | err := dec.Array(DecodeArrayFunc(func(dec *Decoder) error { 77 | var b bool 78 | if err := dec.Bool(&b); err != nil { 79 | return err 80 | } 81 | *s = append(*s, b) 82 | return nil 83 | })) 84 | 85 | if err != nil { 86 | return err 87 | } 88 | return nil 89 | } 90 | -------------------------------------------------------------------------------- /decode_slice_test.go: -------------------------------------------------------------------------------- 1 | package gojay 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | type slicesTestObject struct { 11 | sliceString []string 12 | sliceInt []int 13 | sliceFloat64 []float64 14 | sliceBool []bool 15 | } 16 | 17 | func (s *slicesTestObject) UnmarshalJSONObject(dec *Decoder, k string) error { 18 | switch k { 19 | case "sliceString": 20 | return dec.AddSliceString(&s.sliceString) 21 | case "sliceInt": 22 | return dec.AddSliceInt(&s.sliceInt) 23 | case "sliceFloat64": 24 | return dec.AddSliceFloat64(&s.sliceFloat64) 25 | case "sliceBool": 26 | return dec.AddSliceBool(&s.sliceBool) 27 | } 28 | return nil 29 | } 30 | 31 | func (s *slicesTestObject) NKeys() int { 32 | return 4 33 | } 34 | 35 | func TestDecodeSlices(t *testing.T) { 36 | testCases := []struct { 37 | name string 38 | json string 39 | expectedResult slicesTestObject 40 | err bool 41 | }{ 42 | { 43 | name: "basic slice string", 44 | json: `{ 45 | "sliceString": ["foo","bar"] 46 | }`, 47 | expectedResult: slicesTestObject{ 48 | sliceString: []string{"foo", "bar"}, 49 | }, 50 | }, 51 | { 52 | name: "basic slice bool", 53 | json: `{ 54 | "sliceBool": [true,false] 55 | }`, 56 | expectedResult: slicesTestObject{ 57 | sliceBool: []bool{true, false}, 58 | }, 59 | }, 60 | { 61 | name: "basic slice int", 62 | json: `{ 63 | "sliceInt": [1,2,3] 64 | }`, 65 | expectedResult: slicesTestObject{ 66 | sliceInt: []int{1, 2, 3}, 67 | }, 68 | }, 69 | { 70 | name: "basic slice float64", 71 | json: `{ 72 | "sliceFloat64": [1.3,2.4,3.1] 73 | }`, 74 | expectedResult: slicesTestObject{ 75 | sliceFloat64: []float64{1.3, 2.4, 3.1}, 76 | }, 77 | }, 78 | { 79 | name: "err slice float64", 80 | json: `{ 81 | "sliceFloat64": [1.3",2.4,3.1] 82 | }`, 83 | err: true, 84 | }, 85 | { 86 | name: "err slice str", 87 | json: `{ 88 | "sliceString": [",""] 89 | }`, 90 | err: true, 91 | }, 92 | { 93 | name: "err slice int", 94 | json: `{ 95 | "sliceInt": [1t,2,3] 96 | }`, 97 | err: true, 98 | }, 99 | { 100 | name: "err slice bool", 101 | json: `{ 102 | "sliceBool": [truo,false] 103 | }`, 104 | err: true, 105 | }, 106 | } 107 | 108 | for _, testCase := range testCases { 109 | t.Run( 110 | testCase.name, 111 | func(t *testing.T) { 112 | dec := BorrowDecoder(strings.NewReader(testCase.json)) 113 | var o slicesTestObject 114 | err := dec.Decode(&o) 115 | 116 | if testCase.err { 117 | require.NotNil(t, err, "err should not be nil") 118 | return 119 | } 120 | require.Nil(t, err, "err should be nil") 121 | require.Equal(t, testCase.expectedResult, o) 122 | }, 123 | ) 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /decode_sqlnull.go: -------------------------------------------------------------------------------- 1 | package gojay 2 | 3 | import "database/sql" 4 | 5 | // DecodeSQLNullString decodes a sql.NullString 6 | func (dec *Decoder) DecodeSQLNullString(v *sql.NullString) error { 7 | if dec.isPooled == 1 { 8 | panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) 9 | } 10 | return dec.decodeSQLNullString(v) 11 | } 12 | 13 | func (dec *Decoder) decodeSQLNullString(v *sql.NullString) error { 14 | var str string 15 | if err := dec.decodeString(&str); err != nil { 16 | return err 17 | } 18 | v.String = str 19 | v.Valid = true 20 | return nil 21 | } 22 | 23 | // DecodeSQLNullInt64 decodes a sql.NullInt64 24 | func (dec *Decoder) DecodeSQLNullInt64(v *sql.NullInt64) error { 25 | if dec.isPooled == 1 { 26 | panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) 27 | } 28 | return dec.decodeSQLNullInt64(v) 29 | } 30 | 31 | func (dec *Decoder) decodeSQLNullInt64(v *sql.NullInt64) error { 32 | var i int64 33 | if err := dec.decodeInt64(&i); err != nil { 34 | return err 35 | } 36 | v.Int64 = i 37 | v.Valid = true 38 | return nil 39 | } 40 | 41 | // DecodeSQLNullFloat64 decodes a sql.NullString with the given format 42 | func (dec *Decoder) DecodeSQLNullFloat64(v *sql.NullFloat64) error { 43 | if dec.isPooled == 1 { 44 | panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) 45 | } 46 | return dec.decodeSQLNullFloat64(v) 47 | } 48 | 49 | func (dec *Decoder) decodeSQLNullFloat64(v *sql.NullFloat64) error { 50 | var i float64 51 | if err := dec.decodeFloat64(&i); err != nil { 52 | return err 53 | } 54 | v.Float64 = i 55 | v.Valid = true 56 | return nil 57 | } 58 | 59 | // DecodeSQLNullBool decodes a sql.NullString with the given format 60 | func (dec *Decoder) DecodeSQLNullBool(v *sql.NullBool) error { 61 | if dec.isPooled == 1 { 62 | panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) 63 | } 64 | return dec.decodeSQLNullBool(v) 65 | } 66 | 67 | func (dec *Decoder) decodeSQLNullBool(v *sql.NullBool) error { 68 | var b bool 69 | if err := dec.decodeBool(&b); err != nil { 70 | return err 71 | } 72 | v.Bool = b 73 | v.Valid = true 74 | return nil 75 | } 76 | 77 | // Add Values functions 78 | 79 | // AddSQLNullString decodes the JSON value within an object or an array to qn *sql.NullString 80 | func (dec *Decoder) AddSQLNullString(v *sql.NullString) error { 81 | return dec.SQLNullString(v) 82 | } 83 | 84 | // SQLNullString decodes the JSON value within an object or an array to an *sql.NullString 85 | func (dec *Decoder) SQLNullString(v *sql.NullString) error { 86 | var b *string 87 | if err := dec.StringNull(&b); err != nil { 88 | return err 89 | } 90 | if b == nil { 91 | v.Valid = false 92 | } else { 93 | v.String = *b 94 | v.Valid = true 95 | } 96 | return nil 97 | } 98 | 99 | // AddSQLNullInt64 decodes the JSON value within an object or an array to qn *sql.NullInt64 100 | func (dec *Decoder) AddSQLNullInt64(v *sql.NullInt64) error { 101 | return dec.SQLNullInt64(v) 102 | } 103 | 104 | // SQLNullInt64 decodes the JSON value within an object or an array to an *sql.NullInt64 105 | func (dec *Decoder) SQLNullInt64(v *sql.NullInt64) error { 106 | var b *int64 107 | if err := dec.Int64Null(&b); err != nil { 108 | return err 109 | } 110 | if b == nil { 111 | v.Valid = false 112 | } else { 113 | v.Int64 = *b 114 | v.Valid = true 115 | } 116 | return nil 117 | } 118 | 119 | // AddSQLNullFloat64 decodes the JSON value within an object or an array to qn *sql.NullFloat64 120 | func (dec *Decoder) AddSQLNullFloat64(v *sql.NullFloat64) error { 121 | return dec.SQLNullFloat64(v) 122 | } 123 | 124 | // SQLNullFloat64 decodes the JSON value within an object or an array to an *sql.NullFloat64 125 | func (dec *Decoder) SQLNullFloat64(v *sql.NullFloat64) error { 126 | var b *float64 127 | if err := dec.Float64Null(&b); err != nil { 128 | return err 129 | } 130 | if b == nil { 131 | v.Valid = false 132 | } else { 133 | v.Float64 = *b 134 | v.Valid = true 135 | } 136 | return nil 137 | } 138 | 139 | // AddSQLNullBool decodes the JSON value within an object or an array to an *sql.NullBool 140 | func (dec *Decoder) AddSQLNullBool(v *sql.NullBool) error { 141 | return dec.SQLNullBool(v) 142 | } 143 | 144 | // SQLNullBool decodes the JSON value within an object or an array to an *sql.NullBool 145 | func (dec *Decoder) SQLNullBool(v *sql.NullBool) error { 146 | var b *bool 147 | if err := dec.BoolNull(&b); err != nil { 148 | return err 149 | } 150 | if b == nil { 151 | v.Valid = false 152 | } else { 153 | v.Bool = *b 154 | v.Valid = true 155 | } 156 | return nil 157 | } 158 | -------------------------------------------------------------------------------- /decode_stream.go: -------------------------------------------------------------------------------- 1 | package gojay 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | // UnmarshalerStream is the interface to implement for a slice, an array or a slice 9 | // to decode a line delimited JSON to. 10 | type UnmarshalerStream interface { 11 | UnmarshalStream(*StreamDecoder) error 12 | } 13 | 14 | // Stream is a struct holding the Stream api 15 | var Stream = stream{} 16 | 17 | type stream struct{} 18 | 19 | // A StreamDecoder reads and decodes JSON values from an input stream. 20 | // 21 | // It implements conext.Context and provide a channel to notify interruption. 22 | type StreamDecoder struct { 23 | mux sync.RWMutex 24 | *Decoder 25 | done chan struct{} 26 | deadline *time.Time 27 | } 28 | 29 | // DecodeStream reads the next line delimited JSON-encoded value from the decoder's input (io.Reader) and stores it in the value pointed to by c. 30 | // 31 | // c must implement UnmarshalerStream. Ideally c is a channel. See example for implementation. 32 | // 33 | // See the documentation for Unmarshal for details about the conversion of JSON into a Go value. 34 | func (dec *StreamDecoder) DecodeStream(c UnmarshalerStream) error { 35 | if dec.isPooled == 1 { 36 | panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) 37 | } 38 | if dec.r == nil { 39 | dec.err = NoReaderError("No reader given to decode stream") 40 | close(dec.done) 41 | return dec.err 42 | } 43 | for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { 44 | switch dec.data[dec.cursor] { 45 | case ' ', '\n', '\t', '\r', ',': 46 | continue 47 | default: 48 | // char is not space start reading 49 | for dec.nextChar() != 0 { 50 | // calling unmarshal stream 51 | err := c.UnmarshalStream(dec) 52 | if err != nil { 53 | dec.err = err 54 | close(dec.done) 55 | return err 56 | } 57 | // garbage collects buffer 58 | // we don't want the buffer to grow extensively 59 | dec.data = dec.data[dec.cursor:] 60 | dec.length = dec.length - dec.cursor 61 | dec.cursor = 0 62 | } 63 | // close the done channel to signal the end of the job 64 | close(dec.done) 65 | return nil 66 | } 67 | } 68 | close(dec.done) 69 | dec.mux.Lock() 70 | err := dec.raiseInvalidJSONErr(dec.cursor) 71 | dec.mux.Unlock() 72 | return err 73 | } 74 | 75 | // context.Context implementation 76 | 77 | // Done returns a channel that's closed when work is done. 78 | // It implements context.Context 79 | func (dec *StreamDecoder) Done() <-chan struct{} { 80 | return dec.done 81 | } 82 | 83 | // Deadline returns the time when work done on behalf of this context 84 | // should be canceled. Deadline returns ok==false when no deadline is 85 | // set. Successive calls to Deadline return the same results. 86 | func (dec *StreamDecoder) Deadline() (time.Time, bool) { 87 | if dec.deadline != nil { 88 | return *dec.deadline, true 89 | } 90 | return time.Time{}, false 91 | } 92 | 93 | // SetDeadline sets the deadline 94 | func (dec *StreamDecoder) SetDeadline(t time.Time) { 95 | dec.deadline = &t 96 | } 97 | 98 | // Err returns nil if Done is not yet closed. 99 | // If Done is closed, Err returns a non-nil error explaining why. 100 | // It implements context.Context 101 | func (dec *StreamDecoder) Err() error { 102 | select { 103 | case <-dec.done: 104 | dec.mux.RLock() 105 | defer dec.mux.RUnlock() 106 | return dec.err 107 | default: 108 | return nil 109 | } 110 | } 111 | 112 | // Value implements context.Context 113 | func (dec *StreamDecoder) Value(key interface{}) interface{} { 114 | return nil 115 | } 116 | -------------------------------------------------------------------------------- /decode_stream_pool.go: -------------------------------------------------------------------------------- 1 | package gojay 2 | 3 | import ( 4 | "io" 5 | "sync" 6 | ) 7 | 8 | var streamDecPool = sync.Pool{ 9 | New: newStreamDecoderPool, 10 | } 11 | 12 | // NewDecoder returns a new StreamDecoder. 13 | // It takes an io.Reader implementation as data input. 14 | // It initiates the done channel returned by Done(). 15 | func (s stream) NewDecoder(r io.Reader) *StreamDecoder { 16 | dec := NewDecoder(r) 17 | streamDec := &StreamDecoder{ 18 | Decoder: dec, 19 | done: make(chan struct{}, 1), 20 | mux: sync.RWMutex{}, 21 | } 22 | return streamDec 23 | } 24 | func newStreamDecoderPool() interface{} { 25 | return Stream.NewDecoder(nil) 26 | } 27 | 28 | // BorrowDecoder borrows a StreamDecoder from the pool. 29 | // It takes an io.Reader implementation as data input. 30 | // It initiates the done channel returned by Done(). 31 | // 32 | // If no StreamEncoder is available in the pool, it returns a fresh one 33 | func (s stream) BorrowDecoder(r io.Reader) *StreamDecoder { 34 | return s.borrowDecoder(r, 512) 35 | } 36 | 37 | func (s stream) borrowDecoder(r io.Reader, bufSize int) *StreamDecoder { 38 | streamDec := streamDecPool.Get().(*StreamDecoder) 39 | streamDec.called = 0 40 | streamDec.keysDone = 0 41 | streamDec.cursor = 0 42 | streamDec.err = nil 43 | streamDec.r = r 44 | streamDec.length = 0 45 | streamDec.isPooled = 0 46 | streamDec.done = make(chan struct{}, 1) 47 | if bufSize > 0 { 48 | streamDec.data = make([]byte, bufSize) 49 | } 50 | return streamDec 51 | } 52 | 53 | // Release sends back a Decoder to the pool. 54 | // If a decoder is used after calling Release 55 | // a panic will be raised with an InvalidUsagePooledDecoderError error. 56 | func (dec *StreamDecoder) Release() { 57 | dec.isPooled = 1 58 | streamDecPool.Put(dec) 59 | } 60 | -------------------------------------------------------------------------------- /decode_stream_pool_test.go: -------------------------------------------------------------------------------- 1 | package gojay 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestDecodeStreamDecodePooledDecoderError(t *testing.T) { 10 | // we override the pool chan 11 | dec := Stream.NewDecoder(nil) 12 | dec.Release() 13 | defer func() { 14 | err := recover() 15 | assert.NotNil(t, err, "err shouldnt be nil") 16 | assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError") 17 | }() 18 | var v = 0 19 | dec.Decode(&v) 20 | // make sure it fails if this is called 21 | assert.True(t, false, "should not be called as decoder should have panicked") 22 | } 23 | 24 | func TestDecodeStreamDecodePooledDecoderError1(t *testing.T) { 25 | // we override the pool chan 26 | dec := Stream.NewDecoder(nil) 27 | dec.Release() 28 | defer func() { 29 | err := recover() 30 | assert.NotNil(t, err, "err shouldnt be nil") 31 | assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError") 32 | }() 33 | var v = testSliceStrings{} 34 | dec.DecodeArray(&v) 35 | // make sure they are the same 36 | assert.True(t, false, "should not be called as decoder should have panicked") 37 | } 38 | 39 | func TestDecodeStreamDecodePooledDecoderError2(t *testing.T) { 40 | // we override the pool chan 41 | dec := Stream.NewDecoder(nil) 42 | dec.Release() 43 | defer func() { 44 | err := recover() 45 | assert.NotNil(t, err, "err shouldnt be nil") 46 | assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError") 47 | assert.Equal(t, "Invalid usage of pooled decoder", err.(InvalidUsagePooledDecoderError).Error(), "err should be of type InvalidUsagePooledDecoderError") 48 | }() 49 | var v = TestObj{} 50 | dec.DecodeObject(&v) 51 | // make sure they are the same 52 | assert.True(t, false, "should not be called as decoder should have panicked") 53 | } 54 | 55 | func TestStreamDecoderNewPool(t *testing.T) { 56 | dec := newStreamDecoderPool() 57 | assert.IsType(t, &StreamDecoder{}, dec, "dec should be a *StreamDecoder") 58 | } 59 | -------------------------------------------------------------------------------- /decode_string_unicode.go: -------------------------------------------------------------------------------- 1 | package gojay 2 | 3 | import ( 4 | "unicode/utf16" 5 | "unicode/utf8" 6 | ) 7 | 8 | func (dec *Decoder) getUnicode() (rune, error) { 9 | i := 0 10 | r := rune(0) 11 | for ; (dec.cursor < dec.length || dec.read()) && i < 4; dec.cursor++ { 12 | c := dec.data[dec.cursor] 13 | if c >= '0' && c <= '9' { 14 | r = r*16 + rune(c-'0') 15 | } else if c >= 'a' && c <= 'f' { 16 | r = r*16 + rune(c-'a'+10) 17 | } else if c >= 'A' && c <= 'F' { 18 | r = r*16 + rune(c-'A'+10) 19 | } else { 20 | return 0, InvalidJSONError("Invalid unicode code point") 21 | } 22 | i++ 23 | } 24 | return r, nil 25 | } 26 | 27 | func (dec *Decoder) appendEscapeChar(str []byte, c byte) ([]byte, error) { 28 | switch c { 29 | case 't': 30 | str = append(str, '\t') 31 | case 'n': 32 | str = append(str, '\n') 33 | case 'r': 34 | str = append(str, '\r') 35 | case 'b': 36 | str = append(str, '\b') 37 | case 'f': 38 | str = append(str, '\f') 39 | case '\\': 40 | str = append(str, '\\') 41 | default: 42 | return nil, InvalidJSONError("Invalid JSON") 43 | } 44 | return str, nil 45 | } 46 | 47 | func (dec *Decoder) parseUnicode() ([]byte, error) { 48 | // get unicode after u 49 | r, err := dec.getUnicode() 50 | if err != nil { 51 | return nil, err 52 | } 53 | // no error start making new string 54 | str := make([]byte, 16, 16) 55 | i := 0 56 | // check if code can be a surrogate utf16 57 | if utf16.IsSurrogate(r) { 58 | if dec.cursor >= dec.length && !dec.read() { 59 | return nil, dec.raiseInvalidJSONErr(dec.cursor) 60 | } 61 | c := dec.data[dec.cursor] 62 | if c != '\\' { 63 | i += utf8.EncodeRune(str, r) 64 | return str[:i], nil 65 | } 66 | dec.cursor++ 67 | if dec.cursor >= dec.length && !dec.read() { 68 | return nil, dec.raiseInvalidJSONErr(dec.cursor) 69 | } 70 | c = dec.data[dec.cursor] 71 | if c != 'u' { 72 | i += utf8.EncodeRune(str, r) 73 | str, err = dec.appendEscapeChar(str[:i], c) 74 | if err != nil { 75 | dec.err = err 76 | return nil, err 77 | } 78 | i++ 79 | dec.cursor++ 80 | return str[:i], nil 81 | } 82 | dec.cursor++ 83 | r2, err := dec.getUnicode() 84 | if err != nil { 85 | return nil, err 86 | } 87 | combined := utf16.DecodeRune(r, r2) 88 | if combined == '\uFFFD' { 89 | i += utf8.EncodeRune(str, r) 90 | i += utf8.EncodeRune(str, r2) 91 | } else { 92 | i += utf8.EncodeRune(str, combined) 93 | } 94 | return str[:i], nil 95 | } 96 | i += utf8.EncodeRune(str, r) 97 | return str[:i], nil 98 | } 99 | -------------------------------------------------------------------------------- /decode_time.go: -------------------------------------------------------------------------------- 1 | package gojay 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // DecodeTime decodes time with the given format 8 | func (dec *Decoder) DecodeTime(v *time.Time, format string) error { 9 | if dec.isPooled == 1 { 10 | panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) 11 | } 12 | return dec.decodeTime(v, format) 13 | } 14 | 15 | func (dec *Decoder) decodeTime(v *time.Time, format string) error { 16 | if format == time.RFC3339 { 17 | var ej = make(EmbeddedJSON, 0, 20) 18 | if err := dec.decodeEmbeddedJSON(&ej); err != nil { 19 | return err 20 | } 21 | if err := v.UnmarshalJSON(ej); err != nil { 22 | return err 23 | } 24 | return nil 25 | } 26 | var str string 27 | if err := dec.decodeString(&str); err != nil { 28 | return err 29 | } 30 | tt, err := time.Parse(format, str) 31 | if err != nil { 32 | return err 33 | } 34 | *v = tt 35 | return nil 36 | } 37 | 38 | // Add Values functions 39 | 40 | // AddTime decodes the JSON value within an object or an array to a *time.Time with the given format 41 | func (dec *Decoder) AddTime(v *time.Time, format string) error { 42 | return dec.Time(v, format) 43 | } 44 | 45 | // Time decodes the JSON value within an object or an array to a *time.Time with the given format 46 | func (dec *Decoder) Time(v *time.Time, format string) error { 47 | err := dec.decodeTime(v, format) 48 | if err != nil { 49 | return err 50 | } 51 | dec.called |= 1 52 | return nil 53 | } 54 | -------------------------------------------------------------------------------- /decode_time_test.go: -------------------------------------------------------------------------------- 1 | package gojay 2 | 3 | import ( 4 | "strings" 5 | "sync" 6 | "testing" 7 | "time" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestDecodeTime(t *testing.T) { 13 | testCases := []struct { 14 | name string 15 | json string 16 | format string 17 | err bool 18 | expectedTime string 19 | }{ 20 | { 21 | name: "basic", 22 | json: `"2018-02-18"`, 23 | format: `2006-01-02`, 24 | err: false, 25 | expectedTime: "2018-02-18", 26 | }, 27 | { 28 | name: "basic", 29 | json: `"2017-01-02T15:04:05Z"`, 30 | format: time.RFC3339, 31 | err: false, 32 | expectedTime: "2017-01-02T15:04:05Z", 33 | }, 34 | { 35 | name: "basic", 36 | json: `"2017-01-02T15:04:05ZINVALID"`, 37 | format: time.RFC3339, 38 | err: true, 39 | expectedTime: "", 40 | }, 41 | { 42 | name: "basic", 43 | json: `"2017-01-02T15:04:05ZINVALID`, 44 | format: time.RFC1123, 45 | err: true, 46 | expectedTime: "", 47 | }, 48 | { 49 | name: "basic", 50 | json: `"2017-01-02T15:04:05ZINVALID"`, 51 | format: time.RFC1123, 52 | err: true, 53 | expectedTime: "", 54 | }, 55 | { 56 | name: "basic", 57 | json: `"2017-01-02T15:04:05ZINVALID`, 58 | format: time.RFC3339, 59 | err: true, 60 | expectedTime: "", 61 | }, 62 | } 63 | 64 | for _, testCase := range testCases { 65 | t.Run(testCase.name, func(t *testing.T) { 66 | tm := time.Time{} 67 | dec := NewDecoder(strings.NewReader(testCase.json)) 68 | err := dec.DecodeTime(&tm, testCase.format) 69 | if !testCase.err { 70 | assert.Nil(t, err) 71 | assert.Equal(t, testCase.expectedTime, tm.Format(testCase.format)) 72 | return 73 | } 74 | assert.NotNil(t, err) 75 | }) 76 | } 77 | } 78 | 79 | func TestDecodeAddTime(t *testing.T) { 80 | testCases := []struct { 81 | name string 82 | json string 83 | format string 84 | err bool 85 | expectedTime string 86 | }{ 87 | { 88 | name: "basic", 89 | json: `"2018-02-18"`, 90 | format: `2006-01-02`, 91 | err: false, 92 | expectedTime: "2018-02-18", 93 | }, 94 | { 95 | name: "basic", 96 | json: ` "2017-01-02T15:04:05Z"`, 97 | format: time.RFC3339, 98 | err: false, 99 | expectedTime: "2017-01-02T15:04:05Z", 100 | }, 101 | { 102 | name: "basic", 103 | json: ` "2017-01-02T15:04:05ZINVALID"`, 104 | format: time.RFC3339, 105 | err: true, 106 | expectedTime: "", 107 | }, 108 | } 109 | 110 | for _, testCase := range testCases { 111 | t.Run(testCase.name, func(t *testing.T) { 112 | tm := time.Time{} 113 | dec := NewDecoder(strings.NewReader(testCase.json)) 114 | err := dec.AddTime(&tm, testCase.format) 115 | if !testCase.err { 116 | assert.Nil(t, err) 117 | assert.Equal(t, testCase.expectedTime, tm.Format(testCase.format)) 118 | return 119 | } 120 | assert.NotNil(t, err) 121 | }) 122 | } 123 | } 124 | 125 | func TestDecoderTimePoolError(t *testing.T) { 126 | // reset the pool to make sure it's not full 127 | decPool = sync.Pool{ 128 | New: func() interface{} { 129 | return NewDecoder(nil) 130 | }, 131 | } 132 | dec := NewDecoder(nil) 133 | dec.Release() 134 | defer func() { 135 | err := recover() 136 | assert.NotNil(t, err, "err shouldnt be nil") 137 | assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError") 138 | }() 139 | _ = dec.DecodeTime(&time.Time{}, time.RFC3339) 140 | assert.True(t, false, "should not be called as decoder should have panicked") 141 | } 142 | -------------------------------------------------------------------------------- /decode_unsafe.go: -------------------------------------------------------------------------------- 1 | package gojay 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // Unsafe is the structure holding the unsafe version of the API. 8 | // The difference between unsafe api and regular api is that the regular API 9 | // copies the buffer passed to Unmarshal functions to a new internal buffer. 10 | // Making it safer because internally GoJay uses unsafe.Pointer to transform slice of bytes into a string. 11 | var Unsafe = decUnsafe{} 12 | 13 | type decUnsafe struct{} 14 | 15 | func (u decUnsafe) UnmarshalJSONArray(data []byte, v UnmarshalerJSONArray) error { 16 | dec := borrowDecoder(nil, 0) 17 | defer dec.Release() 18 | dec.data = data 19 | dec.length = len(data) 20 | _, err := dec.decodeArray(v) 21 | return err 22 | } 23 | 24 | func (u decUnsafe) UnmarshalJSONObject(data []byte, v UnmarshalerJSONObject) error { 25 | dec := borrowDecoder(nil, 0) 26 | defer dec.Release() 27 | dec.data = data 28 | dec.length = len(data) 29 | _, err := dec.decodeObject(v) 30 | return err 31 | } 32 | 33 | func (u decUnsafe) Unmarshal(data []byte, v interface{}) error { 34 | var err error 35 | var dec *Decoder 36 | switch vt := v.(type) { 37 | case *string: 38 | dec = borrowDecoder(nil, 0) 39 | dec.length = len(data) 40 | dec.data = data 41 | err = dec.decodeString(vt) 42 | case *int: 43 | dec = borrowDecoder(nil, 0) 44 | dec.length = len(data) 45 | dec.data = data 46 | err = dec.decodeInt(vt) 47 | case *int8: 48 | dec = borrowDecoder(nil, 0) 49 | dec.length = len(data) 50 | dec.data = data 51 | err = dec.decodeInt8(vt) 52 | case *int16: 53 | dec = borrowDecoder(nil, 0) 54 | dec.length = len(data) 55 | dec.data = data 56 | err = dec.decodeInt16(vt) 57 | case *int32: 58 | dec = borrowDecoder(nil, 0) 59 | dec.length = len(data) 60 | dec.data = data 61 | err = dec.decodeInt32(vt) 62 | case *int64: 63 | dec = borrowDecoder(nil, 0) 64 | dec.length = len(data) 65 | dec.data = data 66 | err = dec.decodeInt64(vt) 67 | case *uint8: 68 | dec = borrowDecoder(nil, 0) 69 | dec.length = len(data) 70 | dec.data = data 71 | err = dec.decodeUint8(vt) 72 | case *uint16: 73 | dec = borrowDecoder(nil, 0) 74 | dec.length = len(data) 75 | dec.data = data 76 | err = dec.decodeUint16(vt) 77 | case *uint32: 78 | dec = borrowDecoder(nil, 0) 79 | dec.length = len(data) 80 | dec.data = data 81 | err = dec.decodeUint32(vt) 82 | case *uint64: 83 | dec = borrowDecoder(nil, 0) 84 | dec.length = len(data) 85 | dec.data = data 86 | err = dec.decodeUint64(vt) 87 | case *float64: 88 | dec = borrowDecoder(nil, 0) 89 | dec.length = len(data) 90 | dec.data = data 91 | err = dec.decodeFloat64(vt) 92 | case *float32: 93 | dec = borrowDecoder(nil, 0) 94 | dec.length = len(data) 95 | dec.data = data 96 | err = dec.decodeFloat32(vt) 97 | case *bool: 98 | dec = borrowDecoder(nil, 0) 99 | dec.length = len(data) 100 | dec.data = data 101 | err = dec.decodeBool(vt) 102 | case UnmarshalerJSONObject: 103 | dec = borrowDecoder(nil, 0) 104 | dec.length = len(data) 105 | dec.data = data 106 | _, err = dec.decodeObject(vt) 107 | case UnmarshalerJSONArray: 108 | dec = borrowDecoder(nil, 0) 109 | dec.length = len(data) 110 | dec.data = data 111 | _, err = dec.decodeArray(vt) 112 | default: 113 | return InvalidUnmarshalError(fmt.Sprintf(invalidUnmarshalErrorMsg, vt)) 114 | } 115 | defer dec.Release() 116 | if err != nil { 117 | return err 118 | } 119 | return dec.err 120 | } 121 | -------------------------------------------------------------------------------- /encode.go: -------------------------------------------------------------------------------- 1 | package gojay 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | ) 8 | 9 | var nullBytes = []byte("null") 10 | 11 | // MarshalJSONArray returns the JSON encoding of v, an implementation of MarshalerJSONArray. 12 | // 13 | // 14 | // Example: 15 | // type TestSlice []*TestStruct 16 | // 17 | // func (t TestSlice) MarshalJSONArray(enc *Encoder) { 18 | // for _, e := range t { 19 | // enc.AddObject(e) 20 | // } 21 | // } 22 | // 23 | // func main() { 24 | // test := &TestSlice{ 25 | // &TestStruct{123456}, 26 | // &TestStruct{7890}, 27 | // } 28 | // b, _ := Marshal(test) 29 | // fmt.Println(b) // [{"id":123456},{"id":7890}] 30 | // } 31 | func MarshalJSONArray(v MarshalerJSONArray) ([]byte, error) { 32 | enc := BorrowEncoder(nil) 33 | enc.grow(512) 34 | enc.writeByte('[') 35 | v.(MarshalerJSONArray).MarshalJSONArray(enc) 36 | enc.writeByte(']') 37 | 38 | defer func() { 39 | enc.buf = make([]byte, 0, 512) 40 | enc.Release() 41 | }() 42 | 43 | return enc.buf, nil 44 | } 45 | 46 | // MarshalJSONObject returns the JSON encoding of v, an implementation of MarshalerJSONObject. 47 | // 48 | // Example: 49 | // type Object struct { 50 | // id int 51 | // } 52 | // func (s *Object) MarshalJSONObject(enc *gojay.Encoder) { 53 | // enc.IntKey("id", s.id) 54 | // } 55 | // func (s *Object) IsNil() bool { 56 | // return s == nil 57 | // } 58 | // 59 | // func main() { 60 | // test := &Object{ 61 | // id: 123456, 62 | // } 63 | // b, _ := gojay.Marshal(test) 64 | // fmt.Println(b) // {"id":123456} 65 | // } 66 | func MarshalJSONObject(v MarshalerJSONObject) ([]byte, error) { 67 | enc := BorrowEncoder(nil) 68 | enc.grow(512) 69 | 70 | defer func() { 71 | enc.buf = make([]byte, 0, 512) 72 | enc.Release() 73 | }() 74 | 75 | return enc.encodeObject(v) 76 | } 77 | 78 | // Marshal returns the JSON encoding of v. 79 | // 80 | // If v is nil, not an implementation MarshalerJSONObject or MarshalerJSONArray or not one of the following types: 81 | // string, int, int8, int16, int32, int64, uint8, uint16, uint32, uint64, float64, float32, bool 82 | // Marshal returns an InvalidMarshalError. 83 | func Marshal(v interface{}) ([]byte, error) { 84 | return marshal(v, false) 85 | } 86 | 87 | // MarshalAny returns the JSON encoding of v. 88 | // 89 | // If v is nil, not an implementation MarshalerJSONObject or MarshalerJSONArray or not one of the following types: 90 | // string, int, int8, int16, int32, int64, uint8, uint16, uint32, uint64, float64, float32, bool 91 | // MarshalAny falls back to "json/encoding" package to marshal the value. 92 | func MarshalAny(v interface{}) ([]byte, error) { 93 | return marshal(v, true) 94 | } 95 | 96 | func marshal(v interface{}, any bool) ([]byte, error) { 97 | var ( 98 | enc = BorrowEncoder(nil) 99 | 100 | buf []byte 101 | err error 102 | ) 103 | 104 | defer func() { 105 | enc.buf = make([]byte, 0, 512) 106 | enc.Release() 107 | }() 108 | 109 | buf, err = func() ([]byte, error) { 110 | switch vt := v.(type) { 111 | case MarshalerJSONObject: 112 | return enc.encodeObject(vt) 113 | case MarshalerJSONArray: 114 | return enc.encodeArray(vt) 115 | case string: 116 | return enc.encodeString(vt) 117 | case bool: 118 | return enc.encodeBool(vt) 119 | case int: 120 | return enc.encodeInt(vt) 121 | case int64: 122 | return enc.encodeInt64(vt) 123 | case int32: 124 | return enc.encodeInt(int(vt)) 125 | case int16: 126 | return enc.encodeInt(int(vt)) 127 | case int8: 128 | return enc.encodeInt(int(vt)) 129 | case uint64: 130 | return enc.encodeInt(int(vt)) 131 | case uint32: 132 | return enc.encodeInt(int(vt)) 133 | case uint16: 134 | return enc.encodeInt(int(vt)) 135 | case uint8: 136 | return enc.encodeInt(int(vt)) 137 | case float64: 138 | return enc.encodeFloat(vt) 139 | case float32: 140 | return enc.encodeFloat32(vt) 141 | case *EmbeddedJSON: 142 | return enc.encodeEmbeddedJSON(vt) 143 | default: 144 | if any { 145 | return json.Marshal(vt) 146 | } 147 | 148 | return nil, InvalidMarshalError(fmt.Sprintf(invalidMarshalErrorMsg, vt)) 149 | } 150 | }() 151 | return buf, err 152 | } 153 | 154 | // MarshalerJSONObject is the interface to implement for struct to be encoded 155 | type MarshalerJSONObject interface { 156 | MarshalJSONObject(enc *Encoder) 157 | IsNil() bool 158 | } 159 | 160 | // MarshalerJSONArray is the interface to implement 161 | // for a slice or an array to be encoded 162 | type MarshalerJSONArray interface { 163 | MarshalJSONArray(enc *Encoder) 164 | IsNil() bool 165 | } 166 | 167 | // An Encoder writes JSON values to an output stream. 168 | type Encoder struct { 169 | buf []byte 170 | isPooled byte 171 | w io.Writer 172 | err error 173 | hasKeys bool 174 | keys []string 175 | } 176 | 177 | // AppendBytes allows a modular usage by appending bytes manually to the current state of the buffer. 178 | func (enc *Encoder) AppendBytes(b []byte) { 179 | enc.writeBytes(b) 180 | } 181 | 182 | // AppendByte allows a modular usage by appending a single byte manually to the current state of the buffer. 183 | func (enc *Encoder) AppendByte(b byte) { 184 | enc.writeByte(b) 185 | } 186 | 187 | // Buf returns the Encoder's buffer. 188 | func (enc *Encoder) Buf() []byte { 189 | return enc.buf 190 | } 191 | 192 | // Write writes to the io.Writer and resets the buffer. 193 | func (enc *Encoder) Write() (int, error) { 194 | i, err := enc.w.Write(enc.buf) 195 | enc.buf = enc.buf[:0] 196 | return i, err 197 | } 198 | 199 | func (enc *Encoder) getPreviousRune() byte { 200 | last := len(enc.buf) - 1 201 | return enc.buf[last] 202 | } 203 | -------------------------------------------------------------------------------- /encode_bool.go: -------------------------------------------------------------------------------- 1 | package gojay 2 | 3 | import "strconv" 4 | 5 | // EncodeBool encodes a bool to JSON 6 | func (enc *Encoder) EncodeBool(v bool) error { 7 | if enc.isPooled == 1 { 8 | panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder")) 9 | } 10 | _, _ = enc.encodeBool(v) 11 | _, err := enc.Write() 12 | if err != nil { 13 | enc.err = err 14 | return err 15 | } 16 | return nil 17 | } 18 | 19 | // encodeBool encodes a bool to JSON 20 | func (enc *Encoder) encodeBool(v bool) ([]byte, error) { 21 | enc.grow(5) 22 | if v { 23 | enc.writeString("true") 24 | } else { 25 | enc.writeString("false") 26 | } 27 | return enc.buf, enc.err 28 | } 29 | 30 | // AddBool adds a bool to be encoded, must be used inside a slice or array encoding (does not encode a key) 31 | func (enc *Encoder) AddBool(v bool) { 32 | enc.Bool(v) 33 | } 34 | 35 | // AddBoolOmitEmpty adds a bool to be encoded, must be used inside a slice or array encoding (does not encode a key) 36 | func (enc *Encoder) AddBoolOmitEmpty(v bool) { 37 | enc.BoolOmitEmpty(v) 38 | } 39 | 40 | // AddBoolNullEmpty adds a bool to be encoded, must be used inside a slice or array encoding (does not encode a key) 41 | func (enc *Encoder) AddBoolNullEmpty(v bool) { 42 | enc.BoolNullEmpty(v) 43 | } 44 | 45 | // AddBoolKey adds a bool to be encoded, must be used inside an object as it will encode a key. 46 | func (enc *Encoder) AddBoolKey(key string, v bool) { 47 | enc.BoolKey(key, v) 48 | } 49 | 50 | // AddBoolKeyOmitEmpty adds a bool to be encoded and skips if it is zero value. 51 | // Must be used inside an object as it will encode a key. 52 | func (enc *Encoder) AddBoolKeyOmitEmpty(key string, v bool) { 53 | enc.BoolKeyOmitEmpty(key, v) 54 | } 55 | 56 | // AddBoolKeyNullEmpty adds a bool to be encoded and encodes `null` if it is zero value. 57 | // Must be used inside an object as it will encode a key. 58 | func (enc *Encoder) AddBoolKeyNullEmpty(key string, v bool) { 59 | enc.BoolKeyNullEmpty(key, v) 60 | } 61 | 62 | // Bool adds a bool to be encoded, must be used inside a slice or array encoding (does not encode a key) 63 | func (enc *Encoder) Bool(v bool) { 64 | enc.grow(5) 65 | r := enc.getPreviousRune() 66 | if r != '[' { 67 | enc.writeByte(',') 68 | } 69 | if v { 70 | enc.writeString("true") 71 | } else { 72 | enc.writeString("false") 73 | } 74 | } 75 | 76 | // BoolOmitEmpty adds a bool to be encoded, must be used inside a slice or array encoding (does not encode a key) 77 | func (enc *Encoder) BoolOmitEmpty(v bool) { 78 | if v == false { 79 | return 80 | } 81 | enc.grow(5) 82 | r := enc.getPreviousRune() 83 | if r != '[' { 84 | enc.writeByte(',') 85 | } 86 | enc.writeString("true") 87 | } 88 | 89 | // BoolNullEmpty adds a bool to be encoded, must be used inside a slice or array encoding (does not encode a key) 90 | func (enc *Encoder) BoolNullEmpty(v bool) { 91 | enc.grow(5) 92 | r := enc.getPreviousRune() 93 | if r != '[' { 94 | enc.writeByte(',') 95 | } 96 | if v == false { 97 | enc.writeBytes(nullBytes) 98 | return 99 | } 100 | enc.writeString("true") 101 | } 102 | 103 | // BoolKey adds a bool to be encoded, must be used inside an object as it will encode a key. 104 | func (enc *Encoder) BoolKey(key string, value bool) { 105 | if enc.hasKeys { 106 | if !enc.keyExists(key) { 107 | return 108 | } 109 | } 110 | enc.grow(5 + len(key)) 111 | r := enc.getPreviousRune() 112 | if r != '{' { 113 | enc.writeByte(',') 114 | } 115 | enc.writeByte('"') 116 | enc.writeStringEscape(key) 117 | enc.writeBytes(objKey) 118 | enc.buf = strconv.AppendBool(enc.buf, value) 119 | } 120 | 121 | // BoolKeyOmitEmpty adds a bool to be encoded and skips it if it is zero value. 122 | // Must be used inside an object as it will encode a key. 123 | func (enc *Encoder) BoolKeyOmitEmpty(key string, v bool) { 124 | if enc.hasKeys { 125 | if !enc.keyExists(key) { 126 | return 127 | } 128 | } 129 | if v == false { 130 | return 131 | } 132 | enc.grow(5 + len(key)) 133 | r := enc.getPreviousRune() 134 | if r != '{' { 135 | enc.writeByte(',') 136 | } 137 | enc.writeByte('"') 138 | enc.writeStringEscape(key) 139 | enc.writeBytes(objKey) 140 | enc.buf = strconv.AppendBool(enc.buf, v) 141 | } 142 | 143 | // BoolKeyNullEmpty adds a bool to be encoded and skips it if it is zero value. 144 | // Must be used inside an object as it will encode a key. 145 | func (enc *Encoder) BoolKeyNullEmpty(key string, v bool) { 146 | if enc.hasKeys { 147 | if !enc.keyExists(key) { 148 | return 149 | } 150 | } 151 | enc.grow(5 + len(key)) 152 | r := enc.getPreviousRune() 153 | if r != '{' { 154 | enc.writeByte(',') 155 | } 156 | enc.writeByte('"') 157 | enc.writeStringEscape(key) 158 | enc.writeBytes(objKey) 159 | if v == false { 160 | enc.writeBytes(nullBytes) 161 | return 162 | } 163 | enc.buf = strconv.AppendBool(enc.buf, v) 164 | } 165 | -------------------------------------------------------------------------------- /encode_bool_test.go: -------------------------------------------------------------------------------- 1 | package gojay 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestEncoderBoolMarshalAPI(t *testing.T) { 11 | t.Run("true", func(t *testing.T) { 12 | b, err := Marshal(true) 13 | assert.Nil(t, err, "err must be nil") 14 | assert.Equal(t, "true", string(b), "string(b) must be equal to 'true'") 15 | }) 16 | t.Run("false", func(t *testing.T) { 17 | b, err := Marshal(false) 18 | assert.Nil(t, err, "err must be nil") 19 | assert.Equal(t, "false", string(b), "string(b) must be equal to 'false'") 20 | }) 21 | } 22 | 23 | func TestEncoderBoolEncodeAPI(t *testing.T) { 24 | t.Run("true", func(t *testing.T) { 25 | builder := &strings.Builder{} 26 | enc := BorrowEncoder(builder) 27 | defer enc.Release() 28 | err := enc.EncodeBool(true) 29 | assert.Nil(t, err, "err must be nil") 30 | assert.Equal(t, "true", builder.String(), "string(b) must be equal to 'true'") 31 | }) 32 | t.Run("false", func(t *testing.T) { 33 | builder := &strings.Builder{} 34 | enc := BorrowEncoder(builder) 35 | defer enc.Release() 36 | err := enc.EncodeBool(false) 37 | assert.Nil(t, err, "err must be nil") 38 | assert.Equal(t, "false", builder.String(), "string(b) must be equal to 'false'") 39 | }) 40 | } 41 | 42 | func TestEncoderBoolErrors(t *testing.T) { 43 | t.Run("pool-error", func(t *testing.T) { 44 | builder := &strings.Builder{} 45 | enc := BorrowEncoder(builder) 46 | enc.isPooled = 1 47 | defer func() { 48 | err := recover() 49 | assert.NotNil(t, err, "err shouldnt be nil") 50 | assert.IsType(t, InvalidUsagePooledEncoderError(""), err, "err should be of type InvalidUsagePooledEncoderError") 51 | assert.Equal(t, "Invalid usage of pooled encoder", err.(InvalidUsagePooledEncoderError).Error(), "err should be of type InvalidUsagePooledEncoderError") 52 | }() 53 | _ = enc.EncodeBool(false) 54 | assert.True(t, false, "should not be called as it should have panicked") 55 | }) 56 | t.Run("encode-api-write-error", func(t *testing.T) { 57 | v := true 58 | w := TestWriterError("") 59 | enc := BorrowEncoder(w) 60 | defer enc.Release() 61 | err := enc.EncodeBool(v) 62 | assert.NotNil(t, err, "err should not be nil") 63 | }) 64 | } 65 | 66 | func TestEncoderBoolNullEmpty(t *testing.T) { 67 | var testCases = []struct { 68 | name string 69 | baseJSON string 70 | expectedJSON string 71 | }{ 72 | { 73 | name: "basic 1st elem", 74 | baseJSON: "[", 75 | expectedJSON: "[null,true", 76 | }, 77 | { 78 | name: "basic 2nd elem", 79 | baseJSON: `["test"`, 80 | expectedJSON: `["test",null,true`, 81 | }, 82 | } 83 | for _, testCase := range testCases { 84 | t.Run("true", func(t *testing.T) { 85 | var b strings.Builder 86 | var enc = NewEncoder(&b) 87 | enc.writeString(testCase.baseJSON) 88 | enc.BoolNullEmpty(false) 89 | enc.AddBoolNullEmpty(true) 90 | enc.Write() 91 | assert.Equal(t, testCase.expectedJSON, b.String()) 92 | }) 93 | } 94 | } 95 | 96 | func TestEncoderBoolNullKeyEmpty(t *testing.T) { 97 | var testCases = []struct { 98 | name string 99 | baseJSON string 100 | expectedJSON string 101 | }{ 102 | { 103 | name: "basic 1st elem", 104 | baseJSON: "{", 105 | expectedJSON: `{"foo":null,"bar":true`, 106 | }, 107 | { 108 | name: "basic 2nd elem", 109 | baseJSON: `{"test":"test"`, 110 | expectedJSON: `{"test":"test","foo":null,"bar":true`, 111 | }, 112 | } 113 | for _, testCase := range testCases { 114 | t.Run("true", func(t *testing.T) { 115 | var b strings.Builder 116 | var enc = NewEncoder(&b) 117 | enc.writeString(testCase.baseJSON) 118 | enc.BoolKeyNullEmpty("foo", false) 119 | enc.AddBoolKeyNullEmpty("bar", true) 120 | enc.Write() 121 | assert.Equal(t, testCase.expectedJSON, b.String()) 122 | }) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /encode_builder.go: -------------------------------------------------------------------------------- 1 | package gojay 2 | 3 | const hex = "0123456789abcdef" 4 | 5 | // grow grows b's capacity, if necessary, to guarantee space for 6 | // another n bytes. After grow(n), at least n bytes can be written to b 7 | // without another allocation. If n is negative, grow panics. 8 | func (enc *Encoder) grow(n int) { 9 | if cap(enc.buf)-len(enc.buf) < n { 10 | Buf := make([]byte, len(enc.buf), 2*cap(enc.buf)+n) 11 | copy(Buf, enc.buf) 12 | enc.buf = Buf 13 | } 14 | } 15 | 16 | // Write appends the contents of p to b's Buffer. 17 | // Write always returns len(p), nil. 18 | func (enc *Encoder) writeBytes(p []byte) { 19 | enc.buf = append(enc.buf, p...) 20 | } 21 | 22 | func (enc *Encoder) writeTwoBytes(b1 byte, b2 byte) { 23 | enc.buf = append(enc.buf, b1, b2) 24 | } 25 | 26 | // WriteByte appends the byte c to b's Buffer. 27 | // The returned error is always nil. 28 | func (enc *Encoder) writeByte(c byte) { 29 | enc.buf = append(enc.buf, c) 30 | } 31 | 32 | // WriteString appends the contents of s to b's Buffer. 33 | // It returns the length of s and a nil error. 34 | func (enc *Encoder) writeString(s string) { 35 | enc.buf = append(enc.buf, s...) 36 | } 37 | 38 | func (enc *Encoder) writeStringEscape(s string) { 39 | l := len(s) 40 | for i := 0; i < l; i++ { 41 | c := s[i] 42 | if c >= 0x20 && c != '\\' && c != '"' { 43 | enc.writeByte(c) 44 | continue 45 | } 46 | switch c { 47 | case '\\', '"': 48 | enc.writeTwoBytes('\\', c) 49 | case '\n': 50 | enc.writeTwoBytes('\\', 'n') 51 | case '\f': 52 | enc.writeTwoBytes('\\', 'f') 53 | case '\b': 54 | enc.writeTwoBytes('\\', 'b') 55 | case '\r': 56 | enc.writeTwoBytes('\\', 'r') 57 | case '\t': 58 | enc.writeTwoBytes('\\', 't') 59 | default: 60 | enc.writeString(`\u00`) 61 | enc.writeTwoBytes(hex[c>>4], hex[c&0xF]) 62 | } 63 | continue 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /encode_builder_test.go: -------------------------------------------------------------------------------- 1 | package gojay 2 | -------------------------------------------------------------------------------- /encode_embedded_json.go: -------------------------------------------------------------------------------- 1 | package gojay 2 | 3 | // EncodeEmbeddedJSON encodes an embedded JSON. 4 | // is basically sets the internal buf as the value pointed by v and calls the io.Writer.Write() 5 | func (enc *Encoder) EncodeEmbeddedJSON(v *EmbeddedJSON) error { 6 | if enc.isPooled == 1 { 7 | panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder")) 8 | } 9 | enc.buf = *v 10 | _, err := enc.Write() 11 | if err != nil { 12 | return err 13 | } 14 | return nil 15 | } 16 | 17 | func (enc *Encoder) encodeEmbeddedJSON(v *EmbeddedJSON) ([]byte, error) { 18 | enc.writeBytes(*v) 19 | return enc.buf, nil 20 | } 21 | 22 | // AddEmbeddedJSON adds an EmbeddedJSON to be encoded. 23 | // 24 | // It basically blindly writes the bytes to the final buffer. Therefore, 25 | // it expects the JSON to be of proper format. 26 | func (enc *Encoder) AddEmbeddedJSON(v *EmbeddedJSON) { 27 | enc.grow(len(*v) + 4) 28 | r := enc.getPreviousRune() 29 | if r != '[' { 30 | enc.writeByte(',') 31 | } 32 | enc.writeBytes(*v) 33 | } 34 | 35 | // AddEmbeddedJSONOmitEmpty adds an EmbeddedJSON to be encoded or skips it if nil pointer or empty. 36 | // 37 | // It basically blindly writes the bytes to the final buffer. Therefore, 38 | // it expects the JSON to be of proper format. 39 | func (enc *Encoder) AddEmbeddedJSONOmitEmpty(v *EmbeddedJSON) { 40 | if v == nil || len(*v) == 0 { 41 | return 42 | } 43 | r := enc.getPreviousRune() 44 | if r != '[' { 45 | enc.writeByte(',') 46 | } 47 | enc.writeBytes(*v) 48 | } 49 | 50 | // AddEmbeddedJSONKey adds an EmbeddedJSON and a key to be encoded. 51 | // 52 | // It basically blindly writes the bytes to the final buffer. Therefore, 53 | // it expects the JSON to be of proper format. 54 | func (enc *Encoder) AddEmbeddedJSONKey(key string, v *EmbeddedJSON) { 55 | if enc.hasKeys { 56 | if !enc.keyExists(key) { 57 | return 58 | } 59 | } 60 | enc.grow(len(key) + len(*v) + 5) 61 | r := enc.getPreviousRune() 62 | if r != '{' { 63 | enc.writeByte(',') 64 | } 65 | enc.writeByte('"') 66 | enc.writeStringEscape(key) 67 | enc.writeBytes(objKey) 68 | enc.writeBytes(*v) 69 | } 70 | 71 | // AddEmbeddedJSONKeyOmitEmpty adds an EmbeddedJSON and a key to be encoded or skips it if nil pointer or empty. 72 | // 73 | // It basically blindly writes the bytes to the final buffer. Therefore, 74 | // it expects the JSON to be of proper format. 75 | func (enc *Encoder) AddEmbeddedJSONKeyOmitEmpty(key string, v *EmbeddedJSON) { 76 | if enc.hasKeys { 77 | if !enc.keyExists(key) { 78 | return 79 | } 80 | } 81 | if v == nil || len(*v) == 0 { 82 | return 83 | } 84 | enc.grow(len(key) + len(*v) + 5) 85 | r := enc.getPreviousRune() 86 | if r != '{' { 87 | enc.writeByte(',') 88 | } 89 | enc.writeByte('"') 90 | enc.writeStringEscape(key) 91 | enc.writeBytes(objKey) 92 | enc.writeBytes(*v) 93 | } 94 | -------------------------------------------------------------------------------- /encode_embedded_json_test.go: -------------------------------------------------------------------------------- 1 | package gojay 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func (r *Request) MarshalJSONObject(enc *Encoder) { 11 | enc.AddStringKey("id", r.id) 12 | enc.AddStringKey("method", r.method) 13 | enc.AddEmbeddedJSONKey("params", &r.params) 14 | params2 := EmbeddedJSON([]byte(``)) 15 | enc.AddEmbeddedJSONKeyOmitEmpty("params2", ¶ms2) 16 | params3 := EmbeddedJSON([]byte(`"test"`)) 17 | enc.AddEmbeddedJSONKeyOmitEmpty("params3", ¶ms3) 18 | enc.AddIntKey("more", r.more) 19 | } 20 | 21 | func (r *Request) IsNil() bool { 22 | return r == nil 23 | } 24 | 25 | type EmbeddedJSONArr []EmbeddedJSON 26 | 27 | func (ear EmbeddedJSONArr) MarshalJSONArray(enc *Encoder) { 28 | for _, e := range ear { 29 | enc.AddEmbeddedJSON(&e) 30 | } 31 | } 32 | 33 | func (ear EmbeddedJSONArr) IsNil() bool { 34 | return len(ear) == 0 35 | } 36 | 37 | type EmbeddedJSONOmitEmptyArr []EmbeddedJSON 38 | 39 | func (ear EmbeddedJSONOmitEmptyArr) MarshalJSONArray(enc *Encoder) { 40 | for _, e := range ear { 41 | enc.AddEmbeddedJSONOmitEmpty(&e) 42 | } 43 | } 44 | 45 | func (ear EmbeddedJSONOmitEmptyArr) IsNil() bool { 46 | return len(ear) == 0 47 | } 48 | 49 | func TestEncodingEmbeddedJSON(t *testing.T) { 50 | t.Run("basic-embedded-json", func(t *testing.T) { 51 | ej := EmbeddedJSON([]byte(`"test"`)) 52 | b := &strings.Builder{} 53 | enc := BorrowEncoder(b) 54 | err := enc.Encode(&ej) 55 | assert.Nil(t, err, "err should be nil") 56 | assert.Equal(t, b.String(), `"test"`, "b should be equal to content of EmbeddedJSON") 57 | }) 58 | t.Run("basic-embedded-json-marshal-api", func(t *testing.T) { 59 | ej := EmbeddedJSON([]byte(`"test"`)) 60 | b, err := Marshal(&ej) 61 | assert.Nil(t, err, "err should be nil") 62 | assert.Equal(t, string(b), `"test"`, "b should be equal to content of EmbeddedJSON") 63 | }) 64 | t.Run("object-embedded-json", func(t *testing.T) { 65 | req := Request{ 66 | id: "test", 67 | method: "GET", 68 | params: EmbeddedJSON([]byte(`"test"`)), 69 | } 70 | b := &strings.Builder{} 71 | enc := BorrowEncoder(b) 72 | err := enc.EncodeObject(&req) 73 | assert.Nil(t, err, "err should be nil") 74 | assert.Equal( 75 | t, 76 | b.String(), 77 | `{"id":"test","method":"GET","params":"test","params3":"test","more":0}`, 78 | "b should be equal to content of EmbeddedJSON", 79 | ) 80 | }) 81 | t.Run("array-embedded-json", func(t *testing.T) { 82 | ear := EmbeddedJSONArr{ 83 | []byte(`"test"`), 84 | []byte(`{"test":"test"}`), 85 | } 86 | b := &strings.Builder{} 87 | enc := BorrowEncoder(b) 88 | err := enc.EncodeArray(ear) 89 | assert.Nil(t, err, "err should be nil") 90 | assert.Equal( 91 | t, 92 | b.String(), 93 | `["test",{"test":"test"}]`, 94 | "b should be equal to content of EmbeddedJSON", 95 | ) 96 | }) 97 | t.Run("array-embedded-json-omit-empty", func(t *testing.T) { 98 | ear := EmbeddedJSONOmitEmptyArr{ 99 | []byte(`"test"`), 100 | []byte(``), 101 | []byte(`{"test":"test"}`), 102 | []byte(``), 103 | []byte(`{"test":"test"}`), 104 | } 105 | b := &strings.Builder{} 106 | enc := BorrowEncoder(b) 107 | err := enc.EncodeArray(ear) 108 | assert.Nil(t, err, "err should be nil") 109 | assert.Equal( 110 | t, 111 | b.String(), 112 | `["test",{"test":"test"},{"test":"test"}]`, 113 | "b should be equal to content of EmbeddedJSON", 114 | ) 115 | }) 116 | t.Run("write-error", func(t *testing.T) { 117 | w := TestWriterError("") 118 | v := EmbeddedJSON([]byte(`"test"`)) 119 | enc := NewEncoder(w) 120 | err := enc.EncodeEmbeddedJSON(&v) 121 | assert.NotNil(t, err, "Error should not be nil") 122 | assert.Equal(t, "Test Error", err.Error(), "err.Error() should be 'Test Error'") 123 | }) 124 | t.Run("pool-error", func(t *testing.T) { 125 | v := EmbeddedJSON([]byte(`"test"`)) 126 | enc := BorrowEncoder(nil) 127 | enc.isPooled = 1 128 | defer func() { 129 | err := recover() 130 | assert.NotNil(t, err, "err shouldnt be nil") 131 | assert.IsType(t, InvalidUsagePooledEncoderError(""), err, "err should be of type InvalidUsagePooledEncoderError") 132 | assert.Equal(t, "Invalid usage of pooled encoder", err.(InvalidUsagePooledEncoderError).Error(), "err should be of type InvalidUsagePooledDecoderError") 133 | }() 134 | _ = enc.EncodeEmbeddedJSON(&v) 135 | assert.True(t, false, "should not be called as it should have panicked") 136 | }) 137 | } 138 | -------------------------------------------------------------------------------- /encode_example_test.go: -------------------------------------------------------------------------------- 1 | package gojay_test 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | 8 | "github.com/francoispqt/gojay" 9 | ) 10 | 11 | func ExampleMarshal_string() { 12 | str := "gojay" 13 | d, err := gojay.Marshal(str) 14 | if err != nil { 15 | log.Fatal(err) 16 | } 17 | fmt.Println(string(d)) // "gojay" 18 | } 19 | 20 | func ExampleMarshal_bool() { 21 | b := true 22 | d, err := gojay.Marshal(b) 23 | if err != nil { 24 | log.Fatal(err) 25 | } 26 | fmt.Println(string(d)) // true 27 | } 28 | 29 | func ExampleNewEncoder() { 30 | enc := gojay.BorrowEncoder(os.Stdout) 31 | 32 | var str = "gojay" 33 | err := enc.EncodeString(str) 34 | if err != nil { 35 | log.Fatal(err) 36 | } 37 | // Output: 38 | // "gojay" 39 | } 40 | 41 | func ExampleBorrowEncoder() { 42 | enc := gojay.BorrowEncoder(os.Stdout) 43 | defer enc.Release() 44 | 45 | var str = "gojay" 46 | err := enc.EncodeString(str) 47 | if err != nil { 48 | log.Fatal(err) 49 | } 50 | // Output: 51 | // "gojay" 52 | } 53 | 54 | func ExampleEncoder_EncodeString() { 55 | enc := gojay.BorrowEncoder(os.Stdout) 56 | defer enc.Release() 57 | 58 | var str = "gojay" 59 | err := enc.EncodeString(str) 60 | if err != nil { 61 | log.Fatal(err) 62 | } 63 | // Output: 64 | // "gojay" 65 | } 66 | -------------------------------------------------------------------------------- /encode_interface.go: -------------------------------------------------------------------------------- 1 | package gojay 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // Encode encodes a value to JSON. 8 | // 9 | // If Encode cannot find a way to encode the type to JSON 10 | // it will return an InvalidMarshalError. 11 | func (enc *Encoder) Encode(v interface{}) error { 12 | if enc.isPooled == 1 { 13 | panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder")) 14 | } 15 | switch vt := v.(type) { 16 | case string: 17 | return enc.EncodeString(vt) 18 | case bool: 19 | return enc.EncodeBool(vt) 20 | case MarshalerJSONArray: 21 | return enc.EncodeArray(vt) 22 | case MarshalerJSONObject: 23 | return enc.EncodeObject(vt) 24 | case int: 25 | return enc.EncodeInt(vt) 26 | case int64: 27 | return enc.EncodeInt64(vt) 28 | case int32: 29 | return enc.EncodeInt(int(vt)) 30 | case int8: 31 | return enc.EncodeInt(int(vt)) 32 | case uint64: 33 | return enc.EncodeUint64(vt) 34 | case uint32: 35 | return enc.EncodeInt(int(vt)) 36 | case uint16: 37 | return enc.EncodeInt(int(vt)) 38 | case uint8: 39 | return enc.EncodeInt(int(vt)) 40 | case float64: 41 | return enc.EncodeFloat(vt) 42 | case float32: 43 | return enc.EncodeFloat32(vt) 44 | case *EmbeddedJSON: 45 | return enc.EncodeEmbeddedJSON(vt) 46 | default: 47 | return InvalidMarshalError(fmt.Sprintf(invalidMarshalErrorMsg, vt)) 48 | } 49 | } 50 | 51 | // AddInterface adds an interface{} to be encoded, must be used inside a slice or array encoding (does not encode a key) 52 | func (enc *Encoder) AddInterface(value interface{}) { 53 | switch vt := value.(type) { 54 | case string: 55 | enc.AddString(vt) 56 | case bool: 57 | enc.AddBool(vt) 58 | case MarshalerJSONArray: 59 | enc.AddArray(vt) 60 | case MarshalerJSONObject: 61 | enc.AddObject(vt) 62 | case int: 63 | enc.AddInt(vt) 64 | case int64: 65 | enc.AddInt(int(vt)) 66 | case int32: 67 | enc.AddInt(int(vt)) 68 | case int8: 69 | enc.AddInt(int(vt)) 70 | case uint64: 71 | enc.AddUint64(vt) 72 | case uint32: 73 | enc.AddInt(int(vt)) 74 | case uint16: 75 | enc.AddInt(int(vt)) 76 | case uint8: 77 | enc.AddInt(int(vt)) 78 | case float64: 79 | enc.AddFloat(vt) 80 | case float32: 81 | enc.AddFloat32(vt) 82 | default: 83 | if vt != nil { 84 | enc.err = InvalidMarshalError(fmt.Sprintf(invalidMarshalErrorMsg, vt)) 85 | return 86 | } 87 | return 88 | } 89 | } 90 | 91 | // AddInterfaceKey adds an interface{} to be encoded, must be used inside an object as it will encode a key 92 | func (enc *Encoder) AddInterfaceKey(key string, value interface{}) { 93 | switch vt := value.(type) { 94 | case string: 95 | enc.AddStringKey(key, vt) 96 | case bool: 97 | enc.AddBoolKey(key, vt) 98 | case MarshalerJSONArray: 99 | enc.AddArrayKey(key, vt) 100 | case MarshalerJSONObject: 101 | enc.AddObjectKey(key, vt) 102 | case int: 103 | enc.AddIntKey(key, vt) 104 | case int64: 105 | enc.AddIntKey(key, int(vt)) 106 | case int32: 107 | enc.AddIntKey(key, int(vt)) 108 | case int16: 109 | enc.AddIntKey(key, int(vt)) 110 | case int8: 111 | enc.AddIntKey(key, int(vt)) 112 | case uint64: 113 | enc.AddIntKey(key, int(vt)) 114 | case uint32: 115 | enc.AddIntKey(key, int(vt)) 116 | case uint16: 117 | enc.AddIntKey(key, int(vt)) 118 | case uint8: 119 | enc.AddIntKey(key, int(vt)) 120 | case float64: 121 | enc.AddFloatKey(key, vt) 122 | case float32: 123 | enc.AddFloat32Key(key, vt) 124 | default: 125 | if vt != nil { 126 | enc.err = InvalidMarshalError(fmt.Sprintf(invalidMarshalErrorMsg, vt)) 127 | return 128 | } 129 | return 130 | } 131 | } 132 | 133 | // AddInterfaceKeyOmitEmpty adds an interface{} to be encoded, must be used inside an object as it will encode a key 134 | func (enc *Encoder) AddInterfaceKeyOmitEmpty(key string, v interface{}) { 135 | switch vt := v.(type) { 136 | case string: 137 | enc.AddStringKeyOmitEmpty(key, vt) 138 | case bool: 139 | enc.AddBoolKeyOmitEmpty(key, vt) 140 | case MarshalerJSONArray: 141 | enc.AddArrayKeyOmitEmpty(key, vt) 142 | case MarshalerJSONObject: 143 | enc.AddObjectKeyOmitEmpty(key, vt) 144 | case int: 145 | enc.AddIntKeyOmitEmpty(key, vt) 146 | case int64: 147 | enc.AddIntKeyOmitEmpty(key, int(vt)) 148 | case int32: 149 | enc.AddIntKeyOmitEmpty(key, int(vt)) 150 | case int16: 151 | enc.AddIntKeyOmitEmpty(key, int(vt)) 152 | case int8: 153 | enc.AddIntKeyOmitEmpty(key, int(vt)) 154 | case uint64: 155 | enc.AddIntKeyOmitEmpty(key, int(vt)) 156 | case uint32: 157 | enc.AddIntKeyOmitEmpty(key, int(vt)) 158 | case uint16: 159 | enc.AddIntKeyOmitEmpty(key, int(vt)) 160 | case uint8: 161 | enc.AddIntKeyOmitEmpty(key, int(vt)) 162 | case float64: 163 | enc.AddFloatKeyOmitEmpty(key, vt) 164 | case float32: 165 | enc.AddFloat32KeyOmitEmpty(key, vt) 166 | default: 167 | if vt != nil { 168 | enc.err = InvalidMarshalError(fmt.Sprintf(invalidMarshalErrorMsg, vt)) 169 | return 170 | } 171 | return 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /encode_interface_test.go: -------------------------------------------------------------------------------- 1 | package gojay 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | var encoderTestCases = []struct { 12 | v interface{} 13 | expectations func(t *testing.T, b string, err error) 14 | }{ 15 | { 16 | v: 100, 17 | expectations: func(t *testing.T, b string, err error) { 18 | assert.Nil(t, err, "err should be nil") 19 | assert.Equal(t, "100", b, "b should equal 100") 20 | }, 21 | }, 22 | { 23 | v: int64(100), 24 | expectations: func(t *testing.T, b string, err error) { 25 | assert.Nil(t, err, "err should be nil") 26 | assert.Equal(t, "100", b, "b should equal 100") 27 | }, 28 | }, 29 | { 30 | v: int32(100), 31 | expectations: func(t *testing.T, b string, err error) { 32 | assert.Nil(t, err, "err should be nil") 33 | assert.Equal(t, "100", b, "b should equal 100") 34 | }, 35 | }, 36 | { 37 | v: int8(100), 38 | expectations: func(t *testing.T, b string, err error) { 39 | assert.Nil(t, err, "err should be nil") 40 | assert.Equal(t, "100", b, "b should equal 100") 41 | }, 42 | }, 43 | { 44 | v: uint64(100), 45 | expectations: func(t *testing.T, b string, err error) { 46 | assert.Nil(t, err, "err should be nil") 47 | assert.Equal(t, "100", b, "b should equal 100") 48 | }, 49 | }, 50 | { 51 | v: uint32(100), 52 | expectations: func(t *testing.T, b string, err error) { 53 | assert.Nil(t, err, "err should be nil") 54 | assert.Equal(t, "100", b, "b should equal 100") 55 | }, 56 | }, 57 | { 58 | v: uint16(100), 59 | expectations: func(t *testing.T, b string, err error) { 60 | assert.Nil(t, err, "err should be nil") 61 | assert.Equal(t, "100", b, "b should equal 100") 62 | }, 63 | }, 64 | { 65 | v: uint8(100), 66 | expectations: func(t *testing.T, b string, err error) { 67 | assert.Nil(t, err, "err should be nil") 68 | assert.Equal(t, "100", b, "b should equal 100") 69 | }, 70 | }, 71 | { 72 | v: float64(100.12), 73 | expectations: func(t *testing.T, b string, err error) { 74 | assert.Nil(t, err, "err should be nil") 75 | assert.Equal(t, "100.12", b, "b should equal 100.12") 76 | }, 77 | }, 78 | { 79 | v: float32(100.12), 80 | expectations: func(t *testing.T, b string, err error) { 81 | assert.Nil(t, err, "err should be nil") 82 | assert.Equal(t, "100.12", b, "b should equal 100.12") 83 | }, 84 | }, 85 | { 86 | v: true, 87 | expectations: func(t *testing.T, b string, err error) { 88 | assert.Nil(t, err, "err should be nil") 89 | assert.Equal(t, "true", b, "b should equal true") 90 | }, 91 | }, 92 | { 93 | v: "hello world", 94 | expectations: func(t *testing.T, b string, err error) { 95 | assert.Nil(t, err, "err should be nil") 96 | assert.Equal(t, `"hello world"`, b, `b should equal "hello world"`) 97 | }, 98 | }, 99 | { 100 | v: "hello world", 101 | expectations: func(t *testing.T, b string, err error) { 102 | assert.Nil(t, err, "err should be nil") 103 | assert.Equal(t, `"hello world"`, b, `b should equal "hello world"`) 104 | }, 105 | }, 106 | { 107 | v: &TestEncodingArrStrings{"hello world", "foo bar"}, 108 | expectations: func(t *testing.T, b string, err error) { 109 | assert.Nil(t, err, "err should be nil") 110 | assert.Equal(t, `["hello world","foo bar"]`, b, `b should equal ["hello world","foo bar"]`) 111 | }, 112 | }, 113 | { 114 | v: &testObject{ 115 | "漢字", nil, 1, nil, 1, nil, 1, nil, 1, nil, 1, nil, 116 | 1, nil, 1, nil, 1, nil, 1, nil, 1.1, nil, 1.1, nil, true, nil, 117 | &testObject{}, testSliceInts{}, []interface{}{"h", "o", "l", "a"}, 118 | }, 119 | expectations: func(t *testing.T, b string, err error) { 120 | assert.Nil(t, err, "err should be nil") 121 | assert.Equal(t, `{"testStr":"漢字","testInt":1,"testInt64":1,"testInt32":1,"testInt16":1,"testInt8":1,"testUint64":1,"testUint32":1,"testUint16":1,"testUint8":1,"testFloat64":1.1,"testFloat32":1.1,"testBool":true}`, string(b), `string(b) should equal {"testStr":"漢字","testInt":1,"testInt64":1,"testInt32":1,"testInt16":1,"testInt8":1,"testUint64":1,"testUint32":1,"testUint16":1,"testUint8":1,"testFloat64":1.1,"testFloat32":1.1,"testBool":true}`) 122 | }, 123 | }, 124 | { 125 | v: &struct{}{}, 126 | expectations: func(t *testing.T, b string, err error) { 127 | assert.NotNil(t, err, "err should be nil") 128 | assert.IsType(t, InvalidMarshalError(""), err, "err should be of type InvalidMarshalError") 129 | var s = struct{}{} 130 | assert.Equal(t, fmt.Sprintf(invalidMarshalErrorMsg, &s), err.Error(), "err message should be equal to invalidMarshalErrorMsg") 131 | }, 132 | }, 133 | } 134 | 135 | func TestEncoderInterfaceEncodeAPI(t *testing.T) { 136 | t.Run("encode-all-types", func(t *testing.T) { 137 | for _, test := range encoderTestCases { 138 | builder := &strings.Builder{} 139 | enc := BorrowEncoder(builder) 140 | err := enc.Encode(test.v) 141 | enc.Release() 142 | test.expectations(t, builder.String(), err) 143 | } 144 | }) 145 | t.Run("encode-all-types-write-error", func(t *testing.T) { 146 | v := "" 147 | w := TestWriterError("") 148 | enc := BorrowEncoder(w) 149 | err := enc.Encode(v) 150 | assert.NotNil(t, err, "err should not be nil") 151 | }) 152 | t.Run("encode-all-types-pool-error", func(t *testing.T) { 153 | v := "" 154 | w := TestWriterError("") 155 | enc := BorrowEncoder(w) 156 | enc.isPooled = 1 157 | defer func() { 158 | err := recover() 159 | assert.NotNil(t, err, "err should not be nil") 160 | assert.IsType(t, InvalidUsagePooledEncoderError(""), err, "err should be of type InvalidUsagePooledEncoderError") 161 | }() 162 | _ = enc.Encode(v) 163 | assert.True(t, false, "should not be called as decoder should have panicked") 164 | }) 165 | } 166 | 167 | func TestEncoderInterfaceMarshalAPI(t *testing.T) { 168 | t.Run("marshal-all-types", func(t *testing.T) { 169 | for _, test := range encoderTestCases { 170 | b, err := Marshal(test.v) 171 | test.expectations(t, string(b), err) 172 | } 173 | }) 174 | } 175 | -------------------------------------------------------------------------------- /encode_null.go: -------------------------------------------------------------------------------- 1 | package gojay 2 | 3 | // AddNull adds a `null` to be encoded. Must be used while encoding an array.` 4 | func (enc *Encoder) AddNull() { 5 | enc.Null() 6 | } 7 | 8 | // Null adds a `null` to be encoded. Must be used while encoding an array.` 9 | func (enc *Encoder) Null() { 10 | enc.grow(5) 11 | r := enc.getPreviousRune() 12 | if r != '[' { 13 | enc.writeByte(',') 14 | } 15 | enc.writeBytes(nullBytes) 16 | } 17 | 18 | // AddNullKey adds a `null` to be encoded. Must be used while encoding an array.` 19 | func (enc *Encoder) AddNullKey(key string) { 20 | enc.NullKey(key) 21 | } 22 | 23 | // NullKey adds a `null` to be encoded. Must be used while encoding an array.` 24 | func (enc *Encoder) NullKey(key string) { 25 | if enc.hasKeys { 26 | if !enc.keyExists(key) { 27 | return 28 | } 29 | } 30 | enc.grow(5 + len(key)) 31 | r := enc.getPreviousRune() 32 | if r != '{' { 33 | enc.writeByte(',') 34 | } 35 | enc.writeByte('"') 36 | enc.writeStringEscape(key) 37 | enc.writeBytes(objKey) 38 | enc.writeBytes(nullBytes) 39 | } 40 | -------------------------------------------------------------------------------- /encode_null_test.go: -------------------------------------------------------------------------------- 1 | package gojay 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestEncodeNull(t *testing.T) { 11 | var testCases = []struct { 12 | name string 13 | baseJSON string 14 | expectedJSON string 15 | }{ 16 | { 17 | name: "basic 1st element", 18 | baseJSON: `[`, 19 | expectedJSON: `[null,null`, 20 | }, 21 | { 22 | name: "basic last element", 23 | baseJSON: `["test"`, 24 | expectedJSON: `["test",null,null`, 25 | }, 26 | } 27 | 28 | for _, testCase := range testCases { 29 | t.Run(testCase.name, func(t *testing.T) { 30 | var b strings.Builder 31 | var enc = NewEncoder(&b) 32 | enc.writeString(testCase.baseJSON) 33 | enc.Null() 34 | enc.AddNull() 35 | enc.Write() 36 | assert.Equal(t, testCase.expectedJSON, b.String()) 37 | }) 38 | } 39 | } 40 | 41 | func TestEncodeNullKey(t *testing.T) { 42 | var testCases = []struct { 43 | name string 44 | baseJSON string 45 | expectedJSON string 46 | }{ 47 | { 48 | name: "basic 1st element", 49 | baseJSON: `{`, 50 | expectedJSON: `{"foo":null,"bar":null`, 51 | }, 52 | { 53 | name: "basic last element", 54 | baseJSON: `{"test":"test"`, 55 | expectedJSON: `{"test":"test","foo":null,"bar":null`, 56 | }, 57 | } 58 | 59 | for _, testCase := range testCases { 60 | t.Run(testCase.name, func(t *testing.T) { 61 | var b strings.Builder 62 | var enc = NewEncoder(&b) 63 | enc.writeString(testCase.baseJSON) 64 | enc.NullKey("foo") 65 | enc.AddNullKey("bar") 66 | enc.Write() 67 | assert.Equal(t, testCase.expectedJSON, b.String()) 68 | }) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /encode_number.go: -------------------------------------------------------------------------------- 1 | package gojay 2 | -------------------------------------------------------------------------------- /encode_number_float_test.go: -------------------------------------------------------------------------------- 1 | package gojay 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestEncoderFloat64(t *testing.T) { 11 | var testCasesBasic = []struct { 12 | name string 13 | v float64 14 | expectedJSON string 15 | }{ 16 | { 17 | name: "basic", 18 | v: float64(1), 19 | expectedJSON: "[1,1]", 20 | }, 21 | { 22 | name: "big", 23 | v: float64(0), 24 | expectedJSON: "[0,0]", 25 | }, 26 | } 27 | for _, testCase := range testCasesBasic { 28 | t.Run(testCase.name, func(t *testing.T) { 29 | var b = &strings.Builder{} 30 | var enc = NewEncoder(b) 31 | enc.Encode(EncodeArrayFunc(func(enc *Encoder) { 32 | enc.Float64(testCase.v) 33 | enc.AddFloat64(testCase.v) 34 | })) 35 | assert.Equal(t, testCase.expectedJSON, b.String()) 36 | }) 37 | } 38 | var testCasesOmitEmpty = []struct { 39 | name string 40 | v float64 41 | expectedJSON string 42 | }{ 43 | { 44 | name: "basic", 45 | v: float64(1), 46 | expectedJSON: "[1,1]", 47 | }, 48 | { 49 | name: "big", 50 | v: float64(0), 51 | expectedJSON: "[]", 52 | }, 53 | } 54 | for _, testCase := range testCasesOmitEmpty { 55 | t.Run(testCase.name, func(t *testing.T) { 56 | var b = &strings.Builder{} 57 | var enc = NewEncoder(b) 58 | enc.Encode(EncodeArrayFunc(func(enc *Encoder) { 59 | enc.Float64OmitEmpty(testCase.v) 60 | enc.AddFloat64OmitEmpty(testCase.v) 61 | })) 62 | assert.Equal(t, testCase.expectedJSON, b.String()) 63 | }) 64 | } 65 | var testCasesKeyBasic = []struct { 66 | name string 67 | v float64 68 | expectedJSON string 69 | }{ 70 | { 71 | name: "basic", 72 | v: float64(1), 73 | expectedJSON: `{"foo":1,"bar":1}`, 74 | }, 75 | { 76 | name: "big", 77 | v: float64(0), 78 | expectedJSON: `{"foo":0,"bar":0}`, 79 | }, 80 | } 81 | for _, testCase := range testCasesKeyBasic { 82 | t.Run(testCase.name, func(t *testing.T) { 83 | var b = &strings.Builder{} 84 | var enc = NewEncoder(b) 85 | enc.Encode(EncodeObjectFunc(func(enc *Encoder) { 86 | enc.Float64Key("foo", testCase.v) 87 | enc.AddFloat64Key("bar", testCase.v) 88 | })) 89 | assert.Equal(t, testCase.expectedJSON, b.String()) 90 | }) 91 | } 92 | var testCasesKeyOmitEmpty = []struct { 93 | name string 94 | v float64 95 | expectedJSON string 96 | }{ 97 | { 98 | name: "basic", 99 | v: float64(1), 100 | expectedJSON: `{"foo":1,"bar":1}`, 101 | }, 102 | { 103 | name: "big", 104 | v: float64(0), 105 | expectedJSON: "{}", 106 | }, 107 | } 108 | for _, testCase := range testCasesKeyOmitEmpty { 109 | t.Run(testCase.name, func(t *testing.T) { 110 | var b = &strings.Builder{} 111 | var enc = NewEncoder(b) 112 | enc.Encode(EncodeObjectFunc(func(enc *Encoder) { 113 | enc.Float64KeyOmitEmpty("foo", testCase.v) 114 | enc.AddFloat64KeyOmitEmpty("bar", testCase.v) 115 | })) 116 | assert.Equal(t, testCase.expectedJSON, b.String()) 117 | }) 118 | } 119 | } 120 | 121 | func TestEncoderFloat64NullEmpty(t *testing.T) { 122 | var testCases = []struct { 123 | name string 124 | baseJSON string 125 | expectedJSON string 126 | }{ 127 | { 128 | name: "basic 1st elem", 129 | baseJSON: "[", 130 | expectedJSON: `[null,1`, 131 | }, 132 | { 133 | name: "basic 2nd elem", 134 | baseJSON: `["test"`, 135 | expectedJSON: `["test",null,1`, 136 | }, 137 | } 138 | for _, testCase := range testCases { 139 | t.Run("true", func(t *testing.T) { 140 | var b strings.Builder 141 | var enc = NewEncoder(&b) 142 | enc.writeString(testCase.baseJSON) 143 | enc.FloatNullEmpty(0) 144 | enc.AddFloatNullEmpty(1) 145 | enc.Write() 146 | assert.Equal(t, testCase.expectedJSON, b.String()) 147 | }) 148 | } 149 | } 150 | 151 | func TestEncoderFloat64KeyNullEmpty(t *testing.T) { 152 | var testCases = []struct { 153 | name string 154 | baseJSON string 155 | expectedJSON string 156 | }{ 157 | { 158 | name: "basic 1st elem", 159 | baseJSON: "{", 160 | expectedJSON: `{"foo":null,"bar":1`, 161 | }, 162 | { 163 | name: "basic 2nd elem", 164 | baseJSON: `{"test":"test"`, 165 | expectedJSON: `{"test":"test","foo":null,"bar":1`, 166 | }, 167 | } 168 | for _, testCase := range testCases { 169 | t.Run(testCase.name, func(t *testing.T) { 170 | var b strings.Builder 171 | var enc = NewEncoder(&b) 172 | enc.writeString(testCase.baseJSON) 173 | enc.FloatKeyNullEmpty("foo", 0) 174 | enc.AddFloatKeyNullEmpty("bar", 1) 175 | enc.Write() 176 | assert.Equal(t, testCase.expectedJSON, b.String()) 177 | }) 178 | } 179 | } 180 | 181 | func TestEncoderFloat32NullEmpty(t *testing.T) { 182 | var testCases = []struct { 183 | name string 184 | baseJSON string 185 | expectedJSON string 186 | }{ 187 | { 188 | name: "basic 1st elem", 189 | baseJSON: "[", 190 | expectedJSON: `[null,1`, 191 | }, 192 | { 193 | name: "basic 2nd elem", 194 | baseJSON: `["test"`, 195 | expectedJSON: `["test",null,1`, 196 | }, 197 | } 198 | for _, testCase := range testCases { 199 | t.Run("true", func(t *testing.T) { 200 | var b strings.Builder 201 | var enc = NewEncoder(&b) 202 | enc.writeString(testCase.baseJSON) 203 | enc.Float32NullEmpty(0) 204 | enc.AddFloat32NullEmpty(1) 205 | enc.Write() 206 | assert.Equal(t, testCase.expectedJSON, b.String()) 207 | }) 208 | } 209 | } 210 | 211 | func TestEncoderFloat32KeyNullEmpty(t *testing.T) { 212 | var testCases = []struct { 213 | name string 214 | baseJSON string 215 | expectedJSON string 216 | }{ 217 | { 218 | name: "basic 1st elem", 219 | baseJSON: "{", 220 | expectedJSON: `{"foo":null,"bar":1`, 221 | }, 222 | { 223 | name: "basic 2nd elem", 224 | baseJSON: `{"test":"test"`, 225 | expectedJSON: `{"test":"test","foo":null,"bar":1`, 226 | }, 227 | } 228 | for _, testCase := range testCases { 229 | t.Run(testCase.name, func(t *testing.T) { 230 | var b strings.Builder 231 | var enc = NewEncoder(&b) 232 | enc.writeString(testCase.baseJSON) 233 | enc.Float32KeyNullEmpty("foo", 0) 234 | enc.AddFloat32KeyNullEmpty("bar", 1) 235 | enc.Write() 236 | assert.Equal(t, testCase.expectedJSON, b.String()) 237 | }) 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /encode_pool.go: -------------------------------------------------------------------------------- 1 | package gojay 2 | 3 | import ( 4 | "io" 5 | "sync" 6 | ) 7 | 8 | var encPool = sync.Pool{ 9 | New: func() interface{} { 10 | return NewEncoder(nil) 11 | }, 12 | } 13 | 14 | var streamEncPool = sync.Pool{ 15 | New: func() interface{} { 16 | return Stream.NewEncoder(nil) 17 | }, 18 | } 19 | 20 | func init() { 21 | for i := 0; i < 32; i++ { 22 | encPool.Put(NewEncoder(nil)) 23 | } 24 | for i := 0; i < 32; i++ { 25 | streamEncPool.Put(Stream.NewEncoder(nil)) 26 | } 27 | } 28 | 29 | // NewEncoder returns a new encoder or borrows one from the pool 30 | func NewEncoder(w io.Writer) *Encoder { 31 | return &Encoder{w: w} 32 | } 33 | 34 | // BorrowEncoder borrows an Encoder from the pool. 35 | func BorrowEncoder(w io.Writer) *Encoder { 36 | enc := encPool.Get().(*Encoder) 37 | enc.w = w 38 | enc.buf = enc.buf[:0] 39 | enc.isPooled = 0 40 | enc.err = nil 41 | enc.hasKeys = false 42 | enc.keys = nil 43 | return enc 44 | } 45 | 46 | // Release sends back a Encoder to the pool. 47 | func (enc *Encoder) Release() { 48 | enc.isPooled = 1 49 | encPool.Put(enc) 50 | } 51 | -------------------------------------------------------------------------------- /encode_pool_test.go: -------------------------------------------------------------------------------- 1 | package gojay 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "strconv" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func TestConcurrencyMarshal(t *testing.T) { 12 | var f = func(num int, t *testing.T) { 13 | for { 14 | b, err := Marshal(num) 15 | if err != nil { 16 | log.Fatal(err) 17 | } 18 | 19 | s := string(b) 20 | if n, err := strconv.Atoi(s); err != nil || n != num { 21 | t.Error(fmt.Errorf( 22 | "caught race: %v %v", s, num, 23 | )) 24 | } 25 | } 26 | } 27 | 28 | for i := 0; i < 100; i++ { 29 | go f(i, t) 30 | } 31 | time.Sleep(2 * time.Second) 32 | } 33 | -------------------------------------------------------------------------------- /encode_slice.go: -------------------------------------------------------------------------------- 1 | package gojay 2 | 3 | // AddSliceString marshals the given []string s 4 | func (enc *Encoder) AddSliceString(s []string) { 5 | enc.SliceString(s) 6 | } 7 | 8 | // SliceString marshals the given []string s 9 | func (enc *Encoder) SliceString(s []string) { 10 | enc.Array(EncodeArrayFunc(func(enc *Encoder) { 11 | for _, str := range s { 12 | enc.String(str) 13 | } 14 | })) 15 | } 16 | 17 | // AddSliceStringKey marshals the given []string s 18 | func (enc *Encoder) AddSliceStringKey(k string, s []string) { 19 | enc.SliceStringKey(k, s) 20 | } 21 | 22 | // SliceStringKey marshals the given []string s 23 | func (enc *Encoder) SliceStringKey(k string, s []string) { 24 | enc.ArrayKey(k, EncodeArrayFunc(func(enc *Encoder) { 25 | for _, str := range s { 26 | enc.String(str) 27 | } 28 | })) 29 | } 30 | 31 | // AddSliceInt marshals the given []int s 32 | func (enc *Encoder) AddSliceInt(s []int) { 33 | enc.SliceInt(s) 34 | } 35 | 36 | // SliceInt marshals the given []int s 37 | func (enc *Encoder) SliceInt(s []int) { 38 | enc.Array(EncodeArrayFunc(func(enc *Encoder) { 39 | for _, i := range s { 40 | enc.Int(i) 41 | } 42 | })) 43 | } 44 | 45 | // AddSliceIntKey marshals the given []int s 46 | func (enc *Encoder) AddSliceIntKey(k string, s []int) { 47 | enc.SliceIntKey(k, s) 48 | } 49 | 50 | // SliceIntKey marshals the given []int s 51 | func (enc *Encoder) SliceIntKey(k string, s []int) { 52 | enc.ArrayKey(k, EncodeArrayFunc(func(enc *Encoder) { 53 | for _, i := range s { 54 | enc.Int(i) 55 | } 56 | })) 57 | } 58 | 59 | // AddSliceFloat64 marshals the given []float64 s 60 | func (enc *Encoder) AddSliceFloat64(s []float64) { 61 | enc.SliceFloat64(s) 62 | } 63 | 64 | // SliceFloat64 marshals the given []float64 s 65 | func (enc *Encoder) SliceFloat64(s []float64) { 66 | enc.Array(EncodeArrayFunc(func(enc *Encoder) { 67 | for _, i := range s { 68 | enc.Float64(i) 69 | } 70 | })) 71 | } 72 | 73 | // AddSliceFloat64Key marshals the given []float64 s 74 | func (enc *Encoder) AddSliceFloat64Key(k string, s []float64) { 75 | enc.SliceFloat64Key(k, s) 76 | } 77 | 78 | // SliceFloat64Key marshals the given []float64 s 79 | func (enc *Encoder) SliceFloat64Key(k string, s []float64) { 80 | enc.ArrayKey(k, EncodeArrayFunc(func(enc *Encoder) { 81 | for _, i := range s { 82 | enc.Float64(i) 83 | } 84 | })) 85 | } 86 | 87 | // AddSliceBool marshals the given []bool s 88 | func (enc *Encoder) AddSliceBool(s []bool) { 89 | enc.SliceBool(s) 90 | } 91 | 92 | // SliceBool marshals the given []bool s 93 | func (enc *Encoder) SliceBool(s []bool) { 94 | enc.Array(EncodeArrayFunc(func(enc *Encoder) { 95 | for _, i := range s { 96 | enc.Bool(i) 97 | } 98 | })) 99 | } 100 | 101 | // AddSliceBoolKey marshals the given []bool s 102 | func (enc *Encoder) AddSliceBoolKey(k string, s []bool) { 103 | enc.SliceBoolKey(k, s) 104 | } 105 | 106 | // SliceBoolKey marshals the given []bool s 107 | func (enc *Encoder) SliceBoolKey(k string, s []bool) { 108 | enc.ArrayKey(k, EncodeArrayFunc(func(enc *Encoder) { 109 | for _, i := range s { 110 | enc.Bool(i) 111 | } 112 | })) 113 | } 114 | -------------------------------------------------------------------------------- /encode_slice_test.go: -------------------------------------------------------------------------------- 1 | package gojay 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func (s *slicesTestObject) MarshalJSONObject(enc *Encoder) { 11 | enc.AddSliceStringKey("sliceString", s.sliceString) 12 | enc.AddSliceIntKey("sliceInt", s.sliceInt) 13 | enc.AddSliceFloat64Key("sliceFloat64", s.sliceFloat64) 14 | enc.AddSliceBoolKey("sliceBool", s.sliceBool) 15 | } 16 | 17 | func (s *slicesTestObject) IsNil() bool { 18 | return s == nil 19 | } 20 | 21 | func TestEncodeSlices(t *testing.T) { 22 | testCases := []struct { 23 | name string 24 | json string 25 | obj slicesTestObject 26 | }{ 27 | { 28 | name: "basic slice string", 29 | json: `{ 30 | "sliceString": ["foo","bar"], 31 | "sliceInt": [], 32 | "sliceFloat64": [], 33 | "sliceBool": [] 34 | }`, 35 | obj: slicesTestObject{ 36 | sliceString: []string{"foo", "bar"}, 37 | }, 38 | }, 39 | { 40 | name: "basic slice bool", 41 | json: `{ 42 | "sliceString": [], 43 | "sliceInt": [], 44 | "sliceFloat64": [], 45 | "sliceBool": [true,false] 46 | }`, 47 | obj: slicesTestObject{ 48 | sliceBool: []bool{true, false}, 49 | }, 50 | }, 51 | { 52 | name: "basic slice int", 53 | json: `{ 54 | "sliceString": [], 55 | "sliceFloat64": [], 56 | "sliceInt": [1,2,3], 57 | "sliceBool": [] 58 | }`, 59 | obj: slicesTestObject{ 60 | sliceInt: []int{1, 2, 3}, 61 | }, 62 | }, 63 | { 64 | name: "basic slice float64", 65 | json: `{ 66 | "sliceString": [], 67 | "sliceFloat64": [1.3,2.4,3.1], 68 | "sliceInt": [], 69 | "sliceBool": [] 70 | }`, 71 | obj: slicesTestObject{ 72 | sliceFloat64: []float64{1.3, 2.4, 3.1}, 73 | }, 74 | }, 75 | } 76 | 77 | for _, testCase := range testCases { 78 | t.Run( 79 | testCase.name, 80 | func(t *testing.T) { 81 | b := strings.Builder{} 82 | enc := BorrowEncoder(&b) 83 | err := enc.Encode(&testCase.obj) 84 | require.Nil(t, err, "err should be nil") 85 | require.JSONEq(t, testCase.json, b.String()) 86 | }, 87 | ) 88 | } 89 | } 90 | 91 | type testSliceSliceString [][]string 92 | 93 | func (t testSliceSliceString) MarshalJSONArray(enc *Encoder) { 94 | for _, s := range t { 95 | enc.AddSliceString(s) 96 | } 97 | } 98 | 99 | func (t testSliceSliceString) IsNil() bool { 100 | return t == nil 101 | } 102 | 103 | type testSliceSliceBool [][]bool 104 | 105 | func (t testSliceSliceBool) MarshalJSONArray(enc *Encoder) { 106 | for _, s := range t { 107 | enc.AddSliceBool(s) 108 | } 109 | } 110 | 111 | func (t testSliceSliceBool) IsNil() bool { 112 | return t == nil 113 | } 114 | 115 | type testSliceSliceInt [][]int 116 | 117 | func (t testSliceSliceInt) MarshalJSONArray(enc *Encoder) { 118 | for _, s := range t { 119 | enc.AddSliceInt(s) 120 | } 121 | } 122 | 123 | func (t testSliceSliceInt) IsNil() bool { 124 | return t == nil 125 | } 126 | 127 | type testSliceSliceFloat64 [][]float64 128 | 129 | func (t testSliceSliceFloat64) MarshalJSONArray(enc *Encoder) { 130 | for _, s := range t { 131 | enc.AddSliceFloat64(s) 132 | } 133 | } 134 | 135 | func (t testSliceSliceFloat64) IsNil() bool { 136 | return t == nil 137 | } 138 | 139 | func TestEncodeSliceSlices(t *testing.T) { 140 | testCases := []struct { 141 | name string 142 | s MarshalerJSONArray 143 | json string 144 | }{ 145 | { 146 | name: "slice of strings", 147 | s: testSliceSliceString{ 148 | []string{"foo", "bar"}, 149 | }, 150 | json: `[["foo","bar"]]`, 151 | }, 152 | { 153 | name: "slice of ints", 154 | s: testSliceSliceInt{ 155 | []int{1, 2}, 156 | }, 157 | json: `[[1,2]]`, 158 | }, 159 | { 160 | name: "slice of float", 161 | s: testSliceSliceFloat64{ 162 | []float64{1.1, 1.2}, 163 | }, 164 | json: `[[1.1,1.2]]`, 165 | }, 166 | { 167 | name: "slice of bool", 168 | s: testSliceSliceBool{ 169 | []bool{true, false}, 170 | }, 171 | json: `[[true,false]]`, 172 | }, 173 | } 174 | 175 | for _, testCase := range testCases { 176 | t.Run( 177 | testCase.name, 178 | func(t *testing.T) { 179 | b := strings.Builder{} 180 | enc := BorrowEncoder(&b) 181 | err := enc.Encode(testCase.s) 182 | require.Nil(t, err, "err should be nil") 183 | require.JSONEq(t, testCase.json, b.String()) 184 | }, 185 | ) 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /encode_stream.go: -------------------------------------------------------------------------------- 1 | package gojay 2 | 3 | import ( 4 | "strconv" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | // MarshalerStream is the interface to implement 10 | // to continuously encode of stream of data. 11 | type MarshalerStream interface { 12 | MarshalStream(enc *StreamEncoder) 13 | } 14 | 15 | // A StreamEncoder reads and encodes values to JSON from an input stream. 16 | // 17 | // It implements conext.Context and provide a channel to notify interruption. 18 | type StreamEncoder struct { 19 | mux *sync.RWMutex 20 | *Encoder 21 | nConsumer int 22 | delimiter byte 23 | deadline *time.Time 24 | done chan struct{} 25 | } 26 | 27 | // EncodeStream spins up a defined number of non blocking consumers of the MarshalerStream m. 28 | // 29 | // m must implement MarshalerStream. Ideally m is a channel. See example for implementation. 30 | // 31 | // See the documentation for Marshal for details about the conversion of Go value to JSON. 32 | func (s *StreamEncoder) EncodeStream(m MarshalerStream) { 33 | // if a single consumer, just use this encoder 34 | if s.nConsumer == 1 { 35 | go consume(s, s, m) 36 | return 37 | } 38 | // else use this Encoder only for first consumer 39 | // and use new encoders for other consumers 40 | // this is to avoid concurrent writing to same buffer 41 | // resulting in a weird JSON 42 | go consume(s, s, m) 43 | for i := 1; i < s.nConsumer; i++ { 44 | s.mux.RLock() 45 | select { 46 | case <-s.done: 47 | default: 48 | ss := Stream.borrowEncoder(s.w) 49 | ss.mux.Lock() 50 | ss.done = s.done 51 | ss.buf = make([]byte, 0, 512) 52 | ss.delimiter = s.delimiter 53 | go consume(s, ss, m) 54 | ss.mux.Unlock() 55 | } 56 | s.mux.RUnlock() 57 | } 58 | return 59 | } 60 | 61 | // LineDelimited sets the delimiter to a new line character. 62 | // 63 | // It will add a new line after each JSON marshaled by the MarshalerStream 64 | func (s *StreamEncoder) LineDelimited() *StreamEncoder { 65 | s.delimiter = '\n' 66 | return s 67 | } 68 | 69 | // CommaDelimited sets the delimiter to a comma. 70 | // 71 | // It will add a new line after each JSON marshaled by the MarshalerStream 72 | func (s *StreamEncoder) CommaDelimited() *StreamEncoder { 73 | s.delimiter = ',' 74 | return s 75 | } 76 | 77 | // NConsumer sets the number of non blocking go routine to consume the stream. 78 | func (s *StreamEncoder) NConsumer(n int) *StreamEncoder { 79 | s.nConsumer = n 80 | return s 81 | } 82 | 83 | // Release sends back a Decoder to the pool. 84 | // If a decoder is used after calling Release 85 | // a panic will be raised with an InvalidUsagePooledDecoderError error. 86 | func (s *StreamEncoder) Release() { 87 | s.isPooled = 1 88 | streamEncPool.Put(s) 89 | } 90 | 91 | // Done returns a channel that's closed when work is done. 92 | // It implements context.Context 93 | func (s *StreamEncoder) Done() <-chan struct{} { 94 | return s.done 95 | } 96 | 97 | // Err returns nil if Done is not yet closed. 98 | // If Done is closed, Err returns a non-nil error explaining why. 99 | // It implements context.Context 100 | func (s *StreamEncoder) Err() error { 101 | return s.err 102 | } 103 | 104 | // Deadline returns the time when work done on behalf of this context 105 | // should be canceled. Deadline returns ok==false when no deadline is 106 | // set. Successive calls to Deadline return the same results. 107 | func (s *StreamEncoder) Deadline() (time.Time, bool) { 108 | if s.deadline != nil { 109 | return *s.deadline, true 110 | } 111 | return time.Time{}, false 112 | } 113 | 114 | // SetDeadline sets the deadline 115 | func (s *StreamEncoder) SetDeadline(t time.Time) { 116 | s.deadline = &t 117 | } 118 | 119 | // Value implements context.Context 120 | func (s *StreamEncoder) Value(key interface{}) interface{} { 121 | return nil 122 | } 123 | 124 | // Cancel cancels the consumers of the stream, interrupting the stream encoding. 125 | // 126 | // After calling cancel, Done() will return a closed channel. 127 | func (s *StreamEncoder) Cancel(err error) { 128 | s.mux.Lock() 129 | defer s.mux.Unlock() 130 | 131 | select { 132 | case <-s.done: 133 | default: 134 | s.err = err 135 | close(s.done) 136 | } 137 | } 138 | 139 | // AddObject adds an object to be encoded. 140 | // value must implement MarshalerJSONObject. 141 | func (s *StreamEncoder) AddObject(v MarshalerJSONObject) { 142 | if v.IsNil() { 143 | return 144 | } 145 | s.Encoder.writeByte('{') 146 | v.MarshalJSONObject(s.Encoder) 147 | s.Encoder.writeByte('}') 148 | s.Encoder.writeByte(s.delimiter) 149 | } 150 | 151 | // AddString adds a string to be encoded. 152 | func (s *StreamEncoder) AddString(v string) { 153 | s.Encoder.writeByte('"') 154 | s.Encoder.writeString(v) 155 | s.Encoder.writeByte('"') 156 | s.Encoder.writeByte(s.delimiter) 157 | } 158 | 159 | // AddArray adds an implementation of MarshalerJSONArray to be encoded. 160 | func (s *StreamEncoder) AddArray(v MarshalerJSONArray) { 161 | s.Encoder.writeByte('[') 162 | v.MarshalJSONArray(s.Encoder) 163 | s.Encoder.writeByte(']') 164 | s.Encoder.writeByte(s.delimiter) 165 | } 166 | 167 | // AddInt adds an int to be encoded. 168 | func (s *StreamEncoder) AddInt(value int) { 169 | s.buf = strconv.AppendInt(s.buf, int64(value), 10) 170 | s.Encoder.writeByte(s.delimiter) 171 | } 172 | 173 | // AddFloat64 adds a float64 to be encoded. 174 | func (s *StreamEncoder) AddFloat64(value float64) { 175 | s.buf = strconv.AppendFloat(s.buf, value, 'f', -1, 64) 176 | s.Encoder.writeByte(s.delimiter) 177 | } 178 | 179 | // AddFloat adds a float64 to be encoded. 180 | func (s *StreamEncoder) AddFloat(value float64) { 181 | s.AddFloat64(value) 182 | } 183 | 184 | // Non exposed 185 | 186 | func consume(init *StreamEncoder, s *StreamEncoder, m MarshalerStream) { 187 | defer s.Release() 188 | for { 189 | select { 190 | case <-init.Done(): 191 | return 192 | default: 193 | m.MarshalStream(s) 194 | if s.Encoder.err != nil { 195 | init.Cancel(s.Encoder.err) 196 | return 197 | } 198 | i, err := s.Encoder.Write() 199 | if err != nil || i == 0 { 200 | init.Cancel(err) 201 | return 202 | } 203 | } 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /encode_stream_pool.go: -------------------------------------------------------------------------------- 1 | package gojay 2 | 3 | import ( 4 | "io" 5 | "sync" 6 | ) 7 | 8 | // NewEncoder returns a new StreamEncoder. 9 | // It takes an io.Writer implementation to output data. 10 | // It initiates the done channel returned by Done(). 11 | func (s stream) NewEncoder(w io.Writer) *StreamEncoder { 12 | enc := BorrowEncoder(w) 13 | return &StreamEncoder{Encoder: enc, nConsumer: 1, done: make(chan struct{}, 1), mux: &sync.RWMutex{}} 14 | } 15 | 16 | // BorrowEncoder borrows a StreamEncoder from the pool. 17 | // It takes an io.Writer implementation to output data. 18 | // It initiates the done channel returned by Done(). 19 | // 20 | // If no StreamEncoder is available in the pool, it returns a fresh one 21 | func (s stream) BorrowEncoder(w io.Writer) *StreamEncoder { 22 | streamEnc := streamEncPool.Get().(*StreamEncoder) 23 | streamEnc.w = w 24 | streamEnc.Encoder.err = nil 25 | streamEnc.done = make(chan struct{}, 1) 26 | streamEnc.Encoder.buf = streamEnc.buf[:0] 27 | streamEnc.nConsumer = 1 28 | streamEnc.isPooled = 0 29 | return streamEnc 30 | } 31 | 32 | func (s stream) borrowEncoder(w io.Writer) *StreamEncoder { 33 | streamEnc := streamEncPool.Get().(*StreamEncoder) 34 | streamEnc.isPooled = 0 35 | streamEnc.w = w 36 | streamEnc.Encoder.err = nil 37 | return streamEnc 38 | } 39 | -------------------------------------------------------------------------------- /encode_string.go: -------------------------------------------------------------------------------- 1 | package gojay 2 | 3 | // EncodeString encodes a string to 4 | func (enc *Encoder) EncodeString(s string) error { 5 | if enc.isPooled == 1 { 6 | panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder")) 7 | } 8 | _, _ = enc.encodeString(s) 9 | _, err := enc.Write() 10 | if err != nil { 11 | enc.err = err 12 | return err 13 | } 14 | return nil 15 | } 16 | 17 | // encodeString encodes a string to 18 | func (enc *Encoder) encodeString(v string) ([]byte, error) { 19 | enc.writeByte('"') 20 | enc.writeStringEscape(v) 21 | enc.writeByte('"') 22 | return enc.buf, nil 23 | } 24 | 25 | // AppendString appends a string to the buffer 26 | func (enc *Encoder) AppendString(v string) { 27 | enc.grow(len(v) + 2) 28 | enc.writeByte('"') 29 | enc.writeStringEscape(v) 30 | enc.writeByte('"') 31 | } 32 | 33 | // AddString adds a string to be encoded, must be used inside a slice or array encoding (does not encode a key) 34 | func (enc *Encoder) AddString(v string) { 35 | enc.String(v) 36 | } 37 | 38 | // AddStringOmitEmpty adds a string to be encoded or skips it if it is zero value. 39 | // Must be used inside a slice or array encoding (does not encode a key) 40 | func (enc *Encoder) AddStringOmitEmpty(v string) { 41 | enc.StringOmitEmpty(v) 42 | } 43 | 44 | // AddStringNullEmpty adds a string to be encoded or skips it if it is zero value. 45 | // Must be used inside a slice or array encoding (does not encode a key) 46 | func (enc *Encoder) AddStringNullEmpty(v string) { 47 | enc.StringNullEmpty(v) 48 | } 49 | 50 | // AddStringKey adds a string to be encoded, must be used inside an object as it will encode a key 51 | func (enc *Encoder) AddStringKey(key, v string) { 52 | enc.StringKey(key, v) 53 | } 54 | 55 | // AddStringKeyOmitEmpty adds a string to be encoded or skips it if it is zero value. 56 | // Must be used inside an object as it will encode a key 57 | func (enc *Encoder) AddStringKeyOmitEmpty(key, v string) { 58 | enc.StringKeyOmitEmpty(key, v) 59 | } 60 | 61 | // AddStringKeyNullEmpty adds a string to be encoded or skips it if it is zero value. 62 | // Must be used inside an object as it will encode a key 63 | func (enc *Encoder) AddStringKeyNullEmpty(key, v string) { 64 | enc.StringKeyNullEmpty(key, v) 65 | } 66 | 67 | // String adds a string to be encoded, must be used inside a slice or array encoding (does not encode a key) 68 | func (enc *Encoder) String(v string) { 69 | enc.grow(len(v) + 4) 70 | r := enc.getPreviousRune() 71 | if r != '[' { 72 | enc.writeTwoBytes(',', '"') 73 | } else { 74 | enc.writeByte('"') 75 | } 76 | enc.writeStringEscape(v) 77 | enc.writeByte('"') 78 | } 79 | 80 | // StringOmitEmpty adds a string to be encoded or skips it if it is zero value. 81 | // Must be used inside a slice or array encoding (does not encode a key) 82 | func (enc *Encoder) StringOmitEmpty(v string) { 83 | if v == "" { 84 | return 85 | } 86 | r := enc.getPreviousRune() 87 | if r != '[' { 88 | enc.writeTwoBytes(',', '"') 89 | } else { 90 | enc.writeByte('"') 91 | } 92 | enc.writeStringEscape(v) 93 | enc.writeByte('"') 94 | } 95 | 96 | // StringNullEmpty adds a string to be encoded or skips it if it is zero value. 97 | // Must be used inside a slice or array encoding (does not encode a key) 98 | func (enc *Encoder) StringNullEmpty(v string) { 99 | r := enc.getPreviousRune() 100 | if v == "" { 101 | if r != '[' { 102 | enc.writeByte(',') 103 | enc.writeBytes(nullBytes) 104 | } else { 105 | enc.writeBytes(nullBytes) 106 | } 107 | return 108 | } 109 | if r != '[' { 110 | enc.writeTwoBytes(',', '"') 111 | } else { 112 | enc.writeByte('"') 113 | } 114 | enc.writeStringEscape(v) 115 | enc.writeByte('"') 116 | } 117 | 118 | // StringKey adds a string to be encoded, must be used inside an object as it will encode a key 119 | func (enc *Encoder) StringKey(key, v string) { 120 | if enc.hasKeys { 121 | if !enc.keyExists(key) { 122 | return 123 | } 124 | } 125 | enc.grow(len(key) + len(v) + 5) 126 | r := enc.getPreviousRune() 127 | if r != '{' { 128 | enc.writeTwoBytes(',', '"') 129 | } else { 130 | enc.writeByte('"') 131 | } 132 | enc.writeStringEscape(key) 133 | enc.writeBytes(objKeyStr) 134 | enc.writeStringEscape(v) 135 | enc.writeByte('"') 136 | } 137 | 138 | // StringKeyOmitEmpty adds a string to be encoded or skips it if it is zero value. 139 | // Must be used inside an object as it will encode a key 140 | func (enc *Encoder) StringKeyOmitEmpty(key, v string) { 141 | if enc.hasKeys { 142 | if !enc.keyExists(key) { 143 | return 144 | } 145 | } 146 | if v == "" { 147 | return 148 | } 149 | enc.grow(len(key) + len(v) + 5) 150 | r := enc.getPreviousRune() 151 | if r != '{' { 152 | enc.writeTwoBytes(',', '"') 153 | } else { 154 | enc.writeByte('"') 155 | } 156 | enc.writeStringEscape(key) 157 | enc.writeBytes(objKeyStr) 158 | enc.writeStringEscape(v) 159 | enc.writeByte('"') 160 | } 161 | 162 | // StringKeyNullEmpty adds a string to be encoded or skips it if it is zero value. 163 | // Must be used inside an object as it will encode a key 164 | func (enc *Encoder) StringKeyNullEmpty(key, v string) { 165 | if enc.hasKeys { 166 | if !enc.keyExists(key) { 167 | return 168 | } 169 | } 170 | enc.grow(len(key) + len(v) + 5) 171 | r := enc.getPreviousRune() 172 | if r != '{' { 173 | enc.writeTwoBytes(',', '"') 174 | } else { 175 | enc.writeByte('"') 176 | } 177 | enc.writeStringEscape(key) 178 | enc.writeBytes(objKey) 179 | if v == "" { 180 | enc.writeBytes(nullBytes) 181 | return 182 | } 183 | enc.writeByte('"') 184 | enc.writeStringEscape(v) 185 | enc.writeByte('"') 186 | } 187 | -------------------------------------------------------------------------------- /encode_test.go: -------------------------------------------------------------------------------- 1 | package gojay 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | type TestWriterError string 11 | 12 | func (t TestWriterError) Write(b []byte) (int, error) { 13 | return 0, errors.New("Test Error") 14 | } 15 | 16 | func TestAppendBytes(t *testing.T) { 17 | b := []byte(``) 18 | enc := NewEncoder(nil) 19 | enc.buf = b 20 | enc.AppendBytes([]byte(`true`)) 21 | assert.Equal(t, string(enc.buf), `true`, "string(enc.buf) should equal to true") 22 | } 23 | 24 | func TestAppendByte(t *testing.T) { 25 | b := []byte(``) 26 | enc := NewEncoder(nil) 27 | enc.buf = b 28 | enc.AppendByte(1) 29 | assert.Equal(t, enc.buf[0], uint8(0x1), "b[0] should equal to 1") 30 | } 31 | 32 | func TestAppendString(t *testing.T) { 33 | b := []byte(``) 34 | enc := NewEncoder(nil) 35 | enc.buf = b 36 | enc.AppendString("true") 37 | assert.Equal(t, string(enc.buf), `"true"`, "string(enc.buf) should equal to true") 38 | } 39 | 40 | func TestBuf(t *testing.T) { 41 | b := []byte(`test`) 42 | enc := NewEncoder(nil) 43 | enc.buf = b 44 | assert.Equal(t, b, enc.Buf(), "enc.Buf() should equal to b") 45 | } 46 | -------------------------------------------------------------------------------- /encode_time.go: -------------------------------------------------------------------------------- 1 | package gojay 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // EncodeTime encodes a *time.Time to JSON with the given format 8 | func (enc *Encoder) EncodeTime(t *time.Time, format string) error { 9 | if enc.isPooled == 1 { 10 | panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder")) 11 | } 12 | _, _ = enc.encodeTime(t, format) 13 | _, err := enc.Write() 14 | if err != nil { 15 | return err 16 | } 17 | return nil 18 | } 19 | 20 | // encodeInt encodes an int to JSON 21 | func (enc *Encoder) encodeTime(t *time.Time, format string) ([]byte, error) { 22 | enc.writeByte('"') 23 | enc.buf = t.AppendFormat(enc.buf, format) 24 | enc.writeByte('"') 25 | return enc.buf, nil 26 | } 27 | 28 | // AddTimeKey adds an *time.Time to be encoded with the given format, must be used inside an object as it will encode a key 29 | func (enc *Encoder) AddTimeKey(key string, t *time.Time, format string) { 30 | enc.TimeKey(key, t, format) 31 | } 32 | 33 | // TimeKey adds an *time.Time to be encoded with the given format, must be used inside an object as it will encode a key 34 | func (enc *Encoder) TimeKey(key string, t *time.Time, format string) { 35 | if enc.hasKeys { 36 | if !enc.keyExists(key) { 37 | return 38 | } 39 | } 40 | enc.grow(10 + len(key)) 41 | r := enc.getPreviousRune() 42 | if r != '{' { 43 | enc.writeTwoBytes(',', '"') 44 | } else { 45 | enc.writeByte('"') 46 | } 47 | enc.writeStringEscape(key) 48 | enc.writeBytes(objKeyStr) 49 | enc.buf = t.AppendFormat(enc.buf, format) 50 | enc.writeByte('"') 51 | } 52 | 53 | // AddTime adds an *time.Time to be encoded with the given format, must be used inside a slice or array encoding (does not encode a key) 54 | func (enc *Encoder) AddTime(t *time.Time, format string) { 55 | enc.Time(t, format) 56 | } 57 | 58 | // Time adds an *time.Time to be encoded with the given format, must be used inside a slice or array encoding (does not encode a key) 59 | func (enc *Encoder) Time(t *time.Time, format string) { 60 | enc.grow(10) 61 | r := enc.getPreviousRune() 62 | if r != '[' { 63 | enc.writeByte(',') 64 | } 65 | enc.writeByte('"') 66 | enc.buf = t.AppendFormat(enc.buf, format) 67 | enc.writeByte('"') 68 | } 69 | -------------------------------------------------------------------------------- /encode_time_test.go: -------------------------------------------------------------------------------- 1 | package gojay 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | "time" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestEncodeTime(t *testing.T) { 12 | testCases := []struct { 13 | name string 14 | tt string 15 | format string 16 | expectedJSON string 17 | err bool 18 | }{ 19 | { 20 | name: "basic", 21 | tt: "2018-02-01", 22 | format: "2006-01-02", 23 | expectedJSON: `"2018-02-01"`, 24 | err: false, 25 | }, 26 | } 27 | 28 | for _, testCase := range testCases { 29 | t.Run(testCase.name, func(t *testing.T) { 30 | b := strings.Builder{} 31 | tt, err := time.Parse(testCase.format, testCase.tt) 32 | assert.Nil(t, err) 33 | enc := NewEncoder(&b) 34 | err = enc.EncodeTime(&tt, testCase.format) 35 | if !testCase.err { 36 | assert.Nil(t, err) 37 | assert.Equal(t, testCase.expectedJSON, b.String()) 38 | } 39 | }) 40 | } 41 | t.Run("encode-time-pool-error", func(t *testing.T) { 42 | builder := &strings.Builder{} 43 | enc := NewEncoder(builder) 44 | enc.isPooled = 1 45 | defer func() { 46 | err := recover() 47 | assert.NotNil(t, err, "err should not be nil") 48 | assert.IsType(t, InvalidUsagePooledEncoderError(""), err, "err should be of type InvalidUsagePooledEncoderError") 49 | }() 50 | _ = enc.EncodeTime(&time.Time{}, "") 51 | assert.True(t, false, "should not be called as encoder should have panicked") 52 | }) 53 | t.Run("write-error", func(t *testing.T) { 54 | w := TestWriterError("") 55 | enc := BorrowEncoder(w) 56 | defer enc.Release() 57 | err := enc.EncodeTime(&time.Time{}, "") 58 | assert.NotNil(t, err, "err should not be nil") 59 | }) 60 | } 61 | 62 | func TestAddTimeKey(t *testing.T) { 63 | testCases := []struct { 64 | name string 65 | tt string 66 | format string 67 | expectedJSON string 68 | baseJSON string 69 | err bool 70 | }{ 71 | { 72 | name: "basic", 73 | tt: "2018-02-01", 74 | format: "2006-01-02", 75 | baseJSON: "{", 76 | expectedJSON: `{"test":"2018-02-01"`, 77 | err: false, 78 | }, 79 | { 80 | name: "basic", 81 | tt: "2018-02-01", 82 | format: "2006-01-02", 83 | baseJSON: `{""`, 84 | expectedJSON: `{"","test":"2018-02-01"`, 85 | err: false, 86 | }, 87 | } 88 | 89 | for _, testCase := range testCases { 90 | t.Run(testCase.name, func(t *testing.T) { 91 | b := strings.Builder{} 92 | tt, err := time.Parse(testCase.format, testCase.tt) 93 | assert.Nil(t, err) 94 | enc := NewEncoder(&b) 95 | enc.writeString(testCase.baseJSON) 96 | enc.AddTimeKey("test", &tt, testCase.format) 97 | enc.Write() 98 | if !testCase.err { 99 | assert.Nil(t, err) 100 | assert.Equal(t, testCase.expectedJSON, b.String()) 101 | } 102 | }) 103 | } 104 | } 105 | 106 | func TestAddTime(t *testing.T) { 107 | testCases := []struct { 108 | name string 109 | tt string 110 | format string 111 | expectedJSON string 112 | baseJSON string 113 | err bool 114 | }{ 115 | { 116 | name: "basic", 117 | tt: "2018-02-01", 118 | format: "2006-01-02", 119 | baseJSON: "[", 120 | expectedJSON: `["2018-02-01"`, 121 | err: false, 122 | }, 123 | { 124 | name: "basic", 125 | tt: "2018-02-01", 126 | format: "2006-01-02", 127 | baseJSON: "[", 128 | expectedJSON: `["2018-02-01"`, 129 | err: false, 130 | }, 131 | { 132 | name: "basic", 133 | tt: "2018-02-01", 134 | format: "2006-01-02", 135 | baseJSON: `[""`, 136 | expectedJSON: `["","2018-02-01"`, 137 | err: false, 138 | }, 139 | } 140 | 141 | for _, testCase := range testCases { 142 | t.Run(testCase.name, func(t *testing.T) { 143 | b := strings.Builder{} 144 | tt, err := time.Parse(testCase.format, testCase.tt) 145 | assert.Nil(t, err) 146 | enc := NewEncoder(&b) 147 | enc.writeString(testCase.baseJSON) 148 | enc.AddTime(&tt, testCase.format) 149 | enc.Write() 150 | if !testCase.err { 151 | assert.Nil(t, err) 152 | assert.Equal(t, testCase.expectedJSON, b.String()) 153 | } 154 | }) 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /errors.go: -------------------------------------------------------------------------------- 1 | package gojay 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | const invalidJSONCharErrorMsg = "Invalid JSON, wrong char '%c' found at position %d" 9 | 10 | // InvalidJSONError is a type representing an error returned when 11 | // Decoding encounters invalid JSON. 12 | type InvalidJSONError string 13 | 14 | func (err InvalidJSONError) Error() string { 15 | return string(err) 16 | } 17 | 18 | func (dec *Decoder) raiseInvalidJSONErr(pos int) error { 19 | var c byte 20 | if len(dec.data) > pos { 21 | c = dec.data[pos] 22 | } 23 | dec.err = InvalidJSONError( 24 | fmt.Sprintf( 25 | invalidJSONCharErrorMsg, 26 | c, 27 | pos, 28 | ), 29 | ) 30 | return dec.err 31 | } 32 | 33 | const invalidUnmarshalErrorMsg = "Cannot unmarshal JSON to type '%T'" 34 | 35 | // InvalidUnmarshalError is a type representing an error returned when 36 | // Decoding cannot unmarshal JSON to the receiver type for various reasons. 37 | type InvalidUnmarshalError string 38 | 39 | func (err InvalidUnmarshalError) Error() string { 40 | return string(err) 41 | } 42 | 43 | func (dec *Decoder) makeInvalidUnmarshalErr(v interface{}) error { 44 | return InvalidUnmarshalError( 45 | fmt.Sprintf( 46 | invalidUnmarshalErrorMsg, 47 | v, 48 | ), 49 | ) 50 | } 51 | 52 | const invalidMarshalErrorMsg = "Invalid type %T provided to Marshal" 53 | 54 | // InvalidMarshalError is a type representing an error returned when 55 | // Encoding did not find the proper way to encode 56 | type InvalidMarshalError string 57 | 58 | func (err InvalidMarshalError) Error() string { 59 | return string(err) 60 | } 61 | 62 | // NoReaderError is a type representing an error returned when 63 | // decoding requires a reader and none was given 64 | type NoReaderError string 65 | 66 | func (err NoReaderError) Error() string { 67 | return string(err) 68 | } 69 | 70 | // InvalidUsagePooledDecoderError is a type representing an error returned 71 | // when decoding is called on a still pooled Decoder 72 | type InvalidUsagePooledDecoderError string 73 | 74 | func (err InvalidUsagePooledDecoderError) Error() string { 75 | return string(err) 76 | } 77 | 78 | // InvalidUsagePooledEncoderError is a type representing an error returned 79 | // when decoding is called on a still pooled Encoder 80 | type InvalidUsagePooledEncoderError string 81 | 82 | func (err InvalidUsagePooledEncoderError) Error() string { 83 | return string(err) 84 | } 85 | 86 | // ErrUnmarshalPtrExpected is the error returned when unmarshal expects a pointer value, 87 | // When using `dec.ObjectNull` or `dec.ArrayNull` for example. 88 | var ErrUnmarshalPtrExpected = errors.New("Cannot unmarshal to given value, a pointer is expected") 89 | -------------------------------------------------------------------------------- /examples/encode-decode-map/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "strings" 6 | 7 | "github.com/francoispqt/gojay" 8 | ) 9 | 10 | // define our custom map type implementing MarshalerJSONObject and UnmarshalerJSONObject 11 | type myMap map[string]string 12 | 13 | // Implementing Unmarshaler 14 | func (m myMap) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { 15 | str := "" 16 | err := dec.AddString(&str) 17 | if err != nil { 18 | return err 19 | } 20 | m[k] = str 21 | return nil 22 | } 23 | 24 | // Her we put the number of keys 25 | // If number of keys is unknown return 0, it will parse all keys 26 | func (m myMap) NKeys() int { 27 | return 0 28 | } 29 | 30 | // Implementing Marshaler 31 | func (m myMap) MarshalJSONObject(enc *gojay.Encoder) { 32 | for k, v := range m { 33 | enc.AddStringKey(k, v) 34 | } 35 | } 36 | 37 | func (m myMap) IsNil() bool { 38 | return m == nil 39 | } 40 | 41 | // Using Marshal / Unmarshal API 42 | func marshalAPI(m myMap) error { 43 | b, err := gojay.Marshal(m) 44 | if err != nil { 45 | return err 46 | } 47 | log.Print(string(b)) 48 | 49 | nM := myMap(make(map[string]string)) 50 | err = gojay.Unmarshal(b, nM) 51 | if err != nil { 52 | return err 53 | } 54 | log.Print(nM) 55 | return nil 56 | } 57 | 58 | // Using Encode / Decode API 59 | func encodeAPI(m myMap) error { 60 | // we use strings.Builder as it implements io.Writer 61 | builder := &strings.Builder{} 62 | enc := gojay.BorrowEncoder(builder) 63 | defer enc.Release() 64 | // encode 65 | err := enc.EncodeObject(m) 66 | if err != nil { 67 | return err 68 | } 69 | log.Print(builder.String()) 70 | 71 | // make our new map which will receive the decoded JSON 72 | nM := myMap(make(map[string]string)) 73 | // get our decoder with an io.Reader 74 | dec := gojay.BorrowDecoder(strings.NewReader(builder.String())) 75 | defer dec.Release() 76 | // decode 77 | err = dec.DecodeObject(nM) 78 | if err != nil { 79 | return err 80 | } 81 | log.Print(nM) 82 | return nil 83 | } 84 | 85 | func main() { 86 | // make our map to be encoded 87 | m := myMap(map[string]string{ 88 | "test": "test", 89 | "test2": "test2", 90 | }) 91 | 92 | err := marshalAPI(m) 93 | if err != nil { 94 | log.Fatal(err) 95 | } 96 | err = encodeAPI(m) 97 | if err != nil { 98 | log.Fatal(err) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /examples/fuzz/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: gofuzzbuild 2 | gofuzzbuild: 3 | go-fuzz-build github.com/francoispqt/gojay/examples/fuzz 4 | 5 | .PHONY: gofuzz 6 | gofuzz: 7 | go-fuzz -bin=fuzz-fuzz.zip -workdir=. 8 | 9 | .PHONY: gofuzzclean 10 | gofuzzclean: 11 | rm -rf corpus crashers suppressions fuzz-fuzz.zip -------------------------------------------------------------------------------- /examples/fuzz/main.go: -------------------------------------------------------------------------------- 1 | package fuzz 2 | 3 | import ( 4 | "github.com/francoispqt/gojay" 5 | ) 6 | 7 | type user struct { 8 | id int 9 | created uint64 10 | age float64 11 | name string 12 | email string 13 | friend *user 14 | } 15 | 16 | // implement gojay.UnmarshalerJSONObject 17 | func (u *user) UnmarshalJSONObject(dec *gojay.Decoder, key string) error { 18 | switch key { 19 | case "id": 20 | return dec.Int(&u.id) 21 | case "created": 22 | return dec.Uint64(&u.created) 23 | case "age": 24 | return dec.Float(&u.age) 25 | case "name": 26 | return dec.String(&u.name) 27 | case "email": 28 | return dec.String(&u.email) 29 | case "friend": 30 | uu := &user{} 31 | return dec.Object(uu) 32 | } 33 | return nil 34 | } 35 | func (u *user) NKeys() int { 36 | return 3 37 | } 38 | 39 | func Fuzz(input []byte) int { 40 | u := &user{} 41 | err := gojay.UnmarshalJSONObject(input, u) 42 | if err != nil { 43 | return 0 44 | } 45 | return 1 46 | } 47 | -------------------------------------------------------------------------------- /examples/http-benchmarks/Makefile: -------------------------------------------------------------------------------- 1 | 2 | .PHONY: bench 3 | bench: 4 | wrk -d 20s -s post.lua http://localhost:3000 -------------------------------------------------------------------------------- /examples/http-benchmarks/README.md: -------------------------------------------------------------------------------- 1 | # HTTP BENCHMARKS 2 | 3 | This package has two different implementation of a basic HTTP server in Go. It just takes a JSON body, unmarshals it and marshals it back to the response. 4 | 5 | It required `wrk` benchmarking tool, which you can find here: https://github.com/wg/wrk 6 | 7 | ## How to run 8 | 9 | ### gojay 10 | ```bash 11 | cd /path/to/package && go run gojay/main.go 12 | ``` 13 | Then run: 14 | ```bash 15 | cd /path/to/package && make bench 16 | ``` 17 | 18 | ### standard package (encoding/json) 19 | ```bash 20 | cd /path/to/package && go run standard/main.go 21 | ``` 22 | Then run: 23 | ```bash 24 | cd /path/to/package && make bench 25 | ``` 26 | 27 | ## Results 28 | 29 | Results presented here are ran on a MacBook Pro Mid 2015 2,2 GHz Intel Core i7 with 16G of RAM 30 | 31 | **gojay results:** 32 | ``` 33 | Running 20s test @ http://localhost:3000 34 | 2 threads and 10 connections 35 | Thread Stats Avg Stdev Max +/- Stdev 36 | Latency 298.77us 341.40us 10.52ms 94.13% 37 | Req/Sec 18.88k 1.89k 21.40k 73.63% 38 | 755246 requests in 20.10s, 1.67GB read 39 | Requests/sec: 37573.84 40 | Transfer/sec: 84.85MB 41 | ``` 42 | **standard results:** 43 | ``` 44 | Running 20s test @ http://localhost:3000 45 | 2 threads and 10 connections 46 | Thread Stats Avg Stdev Max +/- Stdev 47 | Latency 613.21us 557.50us 12.65ms 93.88% 48 | Req/Sec 9.18k 423.20 10.10k 80.50% 49 | 365404 requests in 20.00s, 811.60MB read 50 | Requests/sec: 18269.66 51 | Transfer/sec: 40.58MB 52 | ``` -------------------------------------------------------------------------------- /examples/http-benchmarks/gojay/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | 7 | "github.com/francoispqt/gojay" 8 | ) 9 | 10 | func main() { 11 | log.Println("Listening on port 3000") 12 | log.Fatal(http.ListenAndServe(":3000", http.HandlerFunc(handler))) 13 | } 14 | 15 | func handler(w http.ResponseWriter, r *http.Request) { 16 | var body Body 17 | dec := gojay.BorrowDecoder(r.Body) 18 | defer dec.Release() 19 | err := dec.Decode(&body) 20 | if err != nil { 21 | panic(err) 22 | } 23 | w.Header().Set("Content-Type", "application/json") 24 | enc := gojay.BorrowEncoder(w) 25 | defer enc.Release() 26 | err = enc.Encode(&body) 27 | if err != nil { 28 | panic(err) 29 | } 30 | } 31 | 32 | type Body struct { 33 | Colors *colors `json:"colors"` 34 | } 35 | 36 | func (c *Body) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { 37 | switch k { 38 | case "colors": 39 | cols := make(colors, 0) 40 | c.Colors = &cols 41 | return dec.Array(c.Colors) 42 | } 43 | return nil 44 | } 45 | func (b *Body) NKeys() int { 46 | return 1 47 | } 48 | 49 | func (b *Body) MarshalJSONObject(enc *gojay.Encoder) { 50 | enc.ArrayKey("colors", b.Colors) 51 | } 52 | func (b *Body) IsNil() bool { 53 | return b == nil 54 | } 55 | 56 | type colors []*Color 57 | 58 | func (b *colors) UnmarshalJSONArray(dec *gojay.Decoder) error { 59 | color := &Color{} 60 | if err := dec.Object(color); err != nil { 61 | return err 62 | } 63 | *b = append(*b, color) 64 | return nil 65 | } 66 | 67 | func (b *colors) MarshalJSONArray(enc *gojay.Encoder) { 68 | for _, color := range *b { 69 | enc.Object(color) 70 | } 71 | } 72 | 73 | func (b *colors) IsNil() bool { 74 | return len(*b) == 0 75 | } 76 | 77 | type Color struct { 78 | Color string `json:"color,omitempty"` 79 | Category string `json:"category,omitempty"` 80 | Type string `json:"type,omitempty"` 81 | Code *Code `json:"code,omitempty"` 82 | } 83 | 84 | func (b *Color) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { 85 | switch k { 86 | case "color": 87 | return dec.String(&b.Color) 88 | case "category": 89 | return dec.String(&b.Category) 90 | case "type": 91 | return dec.String(&b.Type) 92 | case "code": 93 | b.Code = &Code{} 94 | return dec.Object(b.Code) 95 | } 96 | return nil 97 | } 98 | func (b *Color) NKeys() int { 99 | return 4 100 | } 101 | 102 | func (b *Color) MarshalJSONObject(enc *gojay.Encoder) { 103 | enc.ObjectKey("code", b.Code) 104 | enc.StringKey("color", b.Color) 105 | enc.StringKey("category", b.Category) 106 | enc.StringKey("type", b.Type) 107 | } 108 | func (b *Color) IsNil() bool { 109 | return b == nil 110 | } 111 | 112 | type Code struct { 113 | RGBA *ints `json:"rgba,omitempty"` 114 | Hex string `json:"hex,omitempty"` 115 | } 116 | 117 | func (c *Code) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { 118 | switch k { 119 | case "rgba": 120 | rgba := make(ints, 0) 121 | c.RGBA = &rgba 122 | return dec.Array(&rgba) 123 | case "hex": 124 | return dec.String(&c.Hex) 125 | } 126 | return nil 127 | } 128 | func (b *Code) NKeys() int { 129 | return 2 130 | } 131 | 132 | func (b *Code) MarshalJSONObject(enc *gojay.Encoder) { 133 | enc.ArrayKey("rgba", b.RGBA) 134 | enc.StringKey("hex", b.Hex) 135 | } 136 | func (b *Code) IsNil() bool { 137 | return b == nil 138 | } 139 | 140 | type ints []int 141 | 142 | func (v *ints) UnmarshalJSONArray(dec *gojay.Decoder) error { 143 | var i int 144 | if err := dec.Int(&i); err != nil { 145 | return err 146 | } 147 | *v = append(*v, i) 148 | return nil 149 | } 150 | 151 | func (v *ints) MarshalJSONArray(enc *gojay.Encoder) { 152 | for _, i := range *v { 153 | enc.Int(i) 154 | } 155 | } 156 | func (v *ints) IsNil() bool { 157 | return v == nil || len(*v) == 0 158 | } 159 | -------------------------------------------------------------------------------- /examples/http-benchmarks/post.lua: -------------------------------------------------------------------------------- 1 | wrk.method = "POST" 2 | wrk.body = [=[ 3 | { 4 | "colors": [ 5 | { 6 | "color": "black", 7 | "category": "hue", 8 | "type": "primary", 9 | "code": { 10 | "rgba": [255,255,255,1], 11 | "hex": "#000" 12 | } 13 | }, 14 | { 15 | "color": "white", 16 | "category": "value", 17 | "code": { 18 | "rgba": [0,0,0,1], 19 | "hex": "#FFF" 20 | } 21 | }, 22 | { 23 | "color": "red", 24 | "category": "hue", 25 | "type": "primary", 26 | "code": { 27 | "rgba": [255,0,0,1], 28 | "hex": "#FF0" 29 | } 30 | }, 31 | { 32 | "color": "blue", 33 | "category": "hue", 34 | "type": "primary", 35 | "code": { 36 | "rgba": [0,0,255,1], 37 | "hex": "#00F" 38 | } 39 | }, 40 | { 41 | "color": "yellow", 42 | "category": "hue", 43 | "type": "primary", 44 | "code": { 45 | "rgba": [255,255,0,1], 46 | "hex": "#FF0" 47 | } 48 | }, 49 | { 50 | "color": "green", 51 | "category": "hue", 52 | "type": "secondary", 53 | "code": { 54 | "rgba": [0,255,0,1], 55 | "hex": "#0F0" 56 | } 57 | }, 58 | { 59 | "color": "black", 60 | "category": "hue", 61 | "type": "primary", 62 | "code": { 63 | "rgba": [255,255,255,1], 64 | "hex": "#000" 65 | } 66 | }, 67 | { 68 | "color": "white", 69 | "category": "value", 70 | "code": { 71 | "rgba": [0,0,0,1], 72 | "hex": "#FFF" 73 | } 74 | }, 75 | { 76 | "color": "red", 77 | "category": "hue", 78 | "type": "primary", 79 | "code": { 80 | "rgba": [255,0,0,1], 81 | "hex": "#FF0" 82 | } 83 | }, 84 | { 85 | "color": "blue", 86 | "category": "hue", 87 | "type": "primary", 88 | "code": { 89 | "rgba": [0,0,255,1], 90 | "hex": "#00F" 91 | } 92 | }, 93 | { 94 | "color": "yellow", 95 | "category": "hue", 96 | "type": "primary", 97 | "code": { 98 | "rgba": [255,255,0,1], 99 | "hex": "#FF0" 100 | } 101 | }, 102 | { 103 | "color": "green", 104 | "category": "hue", 105 | "type": "secondary", 106 | "code": { 107 | "rgba": [0,255,0,1], 108 | "hex": "#0F0" 109 | } 110 | }, 111 | { 112 | "color": "black", 113 | "category": "hue", 114 | "type": "primary", 115 | "code": { 116 | "rgba": [255,255,255,1], 117 | "hex": "#000" 118 | } 119 | }, 120 | { 121 | "color": "white", 122 | "category": "value", 123 | "code": { 124 | "rgba": [0,0,0,1], 125 | "hex": "#FFF" 126 | } 127 | }, 128 | { 129 | "color": "red", 130 | "category": "hue", 131 | "type": "primary", 132 | "code": { 133 | "rgba": [255,0,0,1], 134 | "hex": "#FF0" 135 | } 136 | }, 137 | { 138 | "color": "blue", 139 | "category": "hue", 140 | "type": "primary", 141 | "code": { 142 | "rgba": [0,0,255,1], 143 | "hex": "#00F" 144 | } 145 | }, 146 | { 147 | "color": "yellow", 148 | "category": "hue", 149 | "type": "primary", 150 | "code": { 151 | "rgba": [255,255,0,1], 152 | "hex": "#FF0" 153 | } 154 | }, 155 | { 156 | "color": "green", 157 | "category": "hue", 158 | "type": "secondary", 159 | "code": { 160 | "rgba": [0,255,0,1], 161 | "hex": "#0F0" 162 | } 163 | }, 164 | { 165 | "color": "black", 166 | "category": "hue", 167 | "type": "primary", 168 | "code": { 169 | "rgba": [255,255,255,1], 170 | "hex": "#000" 171 | } 172 | }, 173 | { 174 | "color": "white", 175 | "category": "value", 176 | "code": { 177 | "rgba": [0,0,0,1], 178 | "hex": "#FFF" 179 | } 180 | }, 181 | { 182 | "color": "red", 183 | "category": "hue", 184 | "type": "primary", 185 | "code": { 186 | "rgba": [255,0,0,1], 187 | "hex": "#FF0" 188 | } 189 | }, 190 | { 191 | "color": "blue", 192 | "category": "hue", 193 | "type": "primary", 194 | "code": { 195 | "rgba": [0,0,255,1], 196 | "hex": "#00F" 197 | } 198 | }, 199 | { 200 | "color": "yellow", 201 | "category": "hue", 202 | "type": "primary", 203 | "code": { 204 | "rgba": [255,255,0,1], 205 | "hex": "#FF0" 206 | } 207 | }, 208 | { 209 | "color": "green", 210 | "category": "hue", 211 | "type": "secondary", 212 | "code": { 213 | "rgba": [0,255,0,1], 214 | "hex": "#0F0" 215 | } 216 | } 217 | ] 218 | } 219 | ]=] 220 | wrk.headers["Content-Type"] = "application/json" 221 | -------------------------------------------------------------------------------- /examples/http-benchmarks/standard/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net/http" 7 | ) 8 | 9 | func main() { 10 | log.Println("Listening on port 3000") 11 | log.Fatal(http.ListenAndServe(":3000", http.HandlerFunc(handler))) 12 | } 13 | 14 | func handler(w http.ResponseWriter, r *http.Request) { 15 | var body Body 16 | err := json.NewDecoder(r.Body).Decode(&body) 17 | if err != nil { 18 | panic(err) 19 | } 20 | 21 | w.Header().Set("Content-Type", "application/json") 22 | err = json.NewEncoder(w).Encode(body) 23 | if err != nil { 24 | panic(err) 25 | } 26 | } 27 | 28 | type Body struct { 29 | Colors []Color `json:"colors"` 30 | } 31 | 32 | type Color struct { 33 | Color string `json:"color,omitempty"` 34 | Category string `json:"category,omitempty"` 35 | Type string `json:"type,omitempty"` 36 | Code Code `json:"code,omitempty"` 37 | } 38 | 39 | type Code struct { 40 | RGBA []int `json:"rgba,omitempty"` 41 | Hex string `json:"hex,omitempty"` 42 | } 43 | -------------------------------------------------------------------------------- /examples/http-json/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/francoispqt/gojay" 7 | ) 8 | 9 | type message struct { 10 | foo string 11 | bar string 12 | } 13 | 14 | func (m *message) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { 15 | switch k { 16 | case "foo": 17 | return dec.AddString(&m.foo) 18 | case "bar": 19 | return dec.AddString(&m.bar) 20 | } 21 | return nil 22 | } 23 | 24 | func (m *message) NKeys() int { 25 | return 2 26 | } 27 | 28 | func (m *message) MarshalJSONObject(dec *gojay.Encoder) { 29 | dec.AddStringKey("foo", m.foo) 30 | dec.AddStringKey("bar", m.bar) 31 | } 32 | 33 | func (m *message) IsNil() bool { 34 | return m == nil 35 | } 36 | 37 | func home(w http.ResponseWriter, r *http.Request) { 38 | // read body using io.Reader 39 | m := &message{} 40 | dec := gojay.BorrowDecoder(r.Body) 41 | defer dec.Release() 42 | err := dec.DecodeObject(m) 43 | if err != nil { 44 | i, err := w.Write([]byte(err.Error())) 45 | if err != nil || i == 0 { 46 | panic(err) 47 | } 48 | return 49 | } 50 | 51 | // just transform response slightly 52 | m.foo += "hey" 53 | 54 | // return response using io.Writer 55 | enc := gojay.BorrowEncoder(w) 56 | defer enc.Release() 57 | err = enc.Encode(m) 58 | if err != nil { 59 | i, err := w.Write([]byte(err.Error())) 60 | if err != nil || i == 0 { 61 | panic(err) 62 | } 63 | } 64 | return 65 | } 66 | 67 | func main() { 68 | http.HandleFunc("/", home) 69 | if err := http.ListenAndServe(":8080", nil); err != nil { 70 | panic(err) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /examples/websocket/client/client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "github.com/francoispqt/gojay/examples/websocket/comm" 5 | "golang.org/x/net/websocket" 6 | ) 7 | 8 | type client struct { 9 | comm.SenderReceiver 10 | id int 11 | } 12 | 13 | func NewClient(id int) *client { 14 | c := new(client) 15 | c.id = id 16 | return c 17 | } 18 | 19 | func (c *client) Dial(url, origin string) error { 20 | conn, err := websocket.Dial(url, "", origin) 21 | if err != nil { 22 | return err 23 | } 24 | c.Conn = conn 25 | c.Init(10) 26 | return nil 27 | } 28 | -------------------------------------------------------------------------------- /examples/websocket/comm/comm.go: -------------------------------------------------------------------------------- 1 | package comm 2 | 3 | import ( 4 | "errors" 5 | "log" 6 | 7 | "github.com/francoispqt/gojay" 8 | "golang.org/x/net/websocket" 9 | ) 10 | 11 | // A basic message for our WebSocket app 12 | 13 | type Message struct { 14 | Message string 15 | UserName string 16 | } 17 | 18 | func (m *Message) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { 19 | switch k { 20 | case "message": 21 | return dec.AddString(&m.Message) 22 | case "userName": 23 | return dec.AddString(&m.UserName) 24 | } 25 | return nil 26 | } 27 | func (m *Message) NKeys() int { 28 | return 2 29 | } 30 | 31 | func (m *Message) MarshalJSONObject(enc *gojay.Encoder) { 32 | enc.AddStringKey("message", m.Message) 33 | enc.AddStringKey("userName", m.UserName) 34 | } 35 | func (u *Message) IsNil() bool { 36 | return u == nil 37 | } 38 | 39 | // Here are defined our communication types 40 | type Sender chan gojay.MarshalerJSONObject 41 | 42 | func (s Sender) MarshalStream(enc *gojay.StreamEncoder) { 43 | select { 44 | case <-enc.Done(): 45 | return 46 | case m := <-s: 47 | enc.AddObject(m) 48 | } 49 | } 50 | 51 | type Receiver chan *Message 52 | 53 | func (s Receiver) UnmarshalStream(dec *gojay.StreamDecoder) error { 54 | m := &Message{} 55 | if err := dec.AddObject(m); err != nil { 56 | return err 57 | } 58 | s <- m 59 | return nil 60 | } 61 | 62 | type SenderReceiver struct { 63 | Send Sender 64 | Receive Receiver 65 | Dec *gojay.StreamDecoder 66 | Enc *gojay.StreamEncoder 67 | Conn *websocket.Conn 68 | } 69 | 70 | func (sc *SenderReceiver) SetReceiver() { 71 | sc.Receive = Receiver(make(chan *Message)) 72 | sc.Dec = gojay.Stream.BorrowDecoder(sc.Conn) 73 | go sc.Dec.DecodeStream(sc.Receive) 74 | } 75 | 76 | func (sc *SenderReceiver) SetSender(nCons int) { 77 | sc.Send = Sender(make(chan gojay.MarshalerJSONObject)) 78 | sc.Enc = gojay.Stream.BorrowEncoder(sc.Conn).NConsumer(nCons).LineDelimited() 79 | go sc.Enc.EncodeStream(sc.Send) 80 | } 81 | 82 | func (sc *SenderReceiver) SendMessage(m gojay.MarshalerJSONObject) error { 83 | select { 84 | case <-sc.Enc.Done(): 85 | return errors.New("sender closed") 86 | case sc.Send <- m: 87 | log.Print("message sent by client: ", m) 88 | return nil 89 | } 90 | } 91 | 92 | func (c *SenderReceiver) OnMessage(f func(*Message)) error { 93 | for { 94 | select { 95 | case <-c.Dec.Done(): 96 | return errors.New("receiver closed") 97 | case m := <-c.Receive: 98 | f(m) 99 | } 100 | } 101 | } 102 | 103 | func (sc *SenderReceiver) Init(sender int) *SenderReceiver { 104 | sc.SetSender(sender) 105 | sc.SetReceiver() 106 | return sc 107 | } 108 | -------------------------------------------------------------------------------- /examples/websocket/main.go: -------------------------------------------------------------------------------- 1 | // package main simulates a conversation between 2 | // a given set of websocket clients and a server. 3 | // 4 | // It spins up a web socket server. 5 | // On a client's connection it creates a SenderReceiver which handles JSON Stream 6 | // encoding and decoding using gojay's streaming API to abstract JSON communication 7 | // between server and client, only having to handle go values. 8 | // 9 | // To simulate a conversation: 10 | // - the server sends a welcome message to the client 11 | // - when the client receives the message, it sends a message back to the server 12 | // - when the server receives the ack message, it will send a message randomly to a client 13 | // - when the client receives the message, it sends a message back to the server... and so on. 14 | package main 15 | 16 | import ( 17 | "log" 18 | "strconv" 19 | 20 | "github.com/francoispqt/gojay/examples/websocket/client" 21 | "github.com/francoispqt/gojay/examples/websocket/comm" 22 | "github.com/francoispqt/gojay/examples/websocket/server" 23 | ) 24 | 25 | func createServer(done chan error) { 26 | // create our server, with a done signal 27 | s := server.NewServer() 28 | // set our connection handler 29 | s.OnConnection(func(c *server.Client) { 30 | // send welcome message to initiate the conversation 31 | c.SendMessage(&comm.Message{ 32 | UserName: "server", 33 | Message: "Welcome !", 34 | }) 35 | // start handling messages 36 | c.OnMessage(func(m *comm.Message) { 37 | log.Print("message received from client: ", m) 38 | s.BroadCastRandom(c, m) 39 | }) 40 | }) 41 | go s.Listen(":8070", done) 42 | } 43 | 44 | func createClient(url, origin string, i int) { 45 | // create our client 46 | c := client.NewClient(i) 47 | // Dial connection to the WS server 48 | err := c.Dial(url, origin) 49 | if err != nil { 50 | panic(err) 51 | } 52 | str := strconv.Itoa(i) 53 | // Init client's sender and receiver 54 | // Set the OnMessage handler 55 | c.OnMessage(func(m *comm.Message) { 56 | log.Print("client "+str+" received from "+m.UserName+" message: ", m) 57 | c.SendMessage(&comm.Message{ 58 | UserName: str, 59 | Message: "Responding to: " + m.UserName + " | old message: " + m.Message, 60 | }) 61 | }) 62 | } 63 | 64 | // Our main function 65 | func main() { 66 | done := make(chan error) 67 | createServer(done) 68 | // add our clients connection 69 | for i := 0; i < 100; i++ { 70 | i := i 71 | go createClient("ws://localhost:8070/ws", "http://localhost/", i) 72 | } 73 | // handle server's termination 74 | log.Fatal(<-done) 75 | } 76 | -------------------------------------------------------------------------------- /examples/websocket/server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "log" 5 | "math/rand" 6 | "net/http" 7 | "sync" 8 | "time" 9 | 10 | "github.com/francoispqt/gojay/examples/websocket/comm" 11 | "golang.org/x/net/websocket" 12 | ) 13 | 14 | type server struct { 15 | clients []*Client 16 | mux *sync.RWMutex 17 | handle func(c *Client) 18 | } 19 | 20 | type Client struct { 21 | comm.SenderReceiver 22 | server *server 23 | } 24 | 25 | func NewClient(s *server, conn *websocket.Conn) *Client { 26 | sC := new(Client) 27 | sC.Conn = conn 28 | sC.server = s 29 | return sC 30 | } 31 | 32 | func NewServer() *server { 33 | s := new(server) 34 | s.mux = new(sync.RWMutex) 35 | s.clients = make([]*Client, 0, 100) 36 | return s 37 | } 38 | 39 | func (c *Client) Close() { 40 | c.Conn.Close() 41 | } 42 | 43 | func (s *server) Handle(conn *websocket.Conn) { 44 | defer func() { 45 | err := conn.Close() 46 | if err != nil { 47 | log.Fatal(err) 48 | } 49 | }() 50 | c := NewClient(s, conn) 51 | // add our server client to the list of clients 52 | s.mux.Lock() 53 | s.clients = append(s.clients, c) 54 | s.mux.Unlock() 55 | // init Client's sender and receiver 56 | c.Init(10) 57 | s.handle(c) 58 | // block until reader is done 59 | <-c.Dec.Done() 60 | } 61 | 62 | func (s *server) Listen(port string, done chan error) { 63 | http.Handle("/ws", websocket.Handler(s.Handle)) 64 | done <- http.ListenAndServe(port, nil) 65 | } 66 | 67 | func (s *server) OnConnection(h func(c *Client)) { 68 | s.handle = h 69 | } 70 | 71 | func random(min, max int) int { 72 | rand.Seed(time.Now().Unix()) 73 | return rand.Intn(max-min) + min 74 | } 75 | 76 | func (s *server) BroadCastRandom(sC *Client, m *comm.Message) { 77 | m.Message = "Random message" 78 | s.mux.RLock() 79 | r := random(0, len(s.clients)) 80 | s.clients[r].SendMessage(m) 81 | s.mux.RUnlock() 82 | } 83 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/francoispqt/gojay 2 | 3 | go 1.12 4 | 5 | require ( 6 | cloud.google.com/go v0.37.0 // indirect 7 | github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23 8 | github.com/go-errors/errors v1.0.1 9 | github.com/golang/protobuf v1.3.1 // indirect 10 | github.com/json-iterator/go v1.1.6 11 | github.com/lunixbochs/vtclean v1.0.0 // indirect 12 | github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe 13 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 14 | github.com/modern-go/reflect2 v1.0.1 // indirect 15 | github.com/pkg/errors v0.8.1 // indirect 16 | github.com/stretchr/testify v1.2.2 17 | github.com/viant/assertly v0.4.8 18 | github.com/viant/toolbox v0.24.0 19 | golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a // indirect 20 | golang.org/x/net v0.0.0-20190313220215-9f648a60d977 21 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 // indirect 22 | golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f // indirect 23 | gopkg.in/yaml.v2 v2.2.2 // indirect 24 | ) 25 | -------------------------------------------------------------------------------- /gojay.go: -------------------------------------------------------------------------------- 1 | // Package gojay implements encoding and decoding of JSON as defined in RFC 7159. 2 | // The mapping between JSON and Go values is described 3 | // in the documentation for the Marshal and Unmarshal functions. 4 | // 5 | // It aims at performance and usability by relying on simple interfaces 6 | // to decode and encode structures, slices, arrays and even channels. 7 | // 8 | // On top of the simple interfaces to implement, gojay provides lots of helpers to decode and encode 9 | // multiple of different types natively such as bit.Int, sql.NullString or time.Time 10 | package gojay 11 | -------------------------------------------------------------------------------- /gojay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/francoispqt/gojay/1398296d938f9fae26750ddc2fe356b6d897f799/gojay.png -------------------------------------------------------------------------------- /gojay/README.md: -------------------------------------------------------------------------------- 1 | # Gojay code generator 2 | This package provides a command line tool to generate gojay's marshaling and unmarshaling interface implementation for custom struct type(s) 3 | 4 | 5 | ## Get started 6 | 7 | ```sh 8 | go install github.com/francoispqt/gojay/gojay 9 | ``` 10 | 11 | ## Generate code 12 | 13 | ### Basic command 14 | The basic command is straightforward and easy to use: 15 | ```sh 16 | cd $GOPATH/src/github.com/user/project 17 | gojay -s . -p true -t MyType -o output.go 18 | ``` 19 | If you just want to the output to stdout, omit the -o flag. 20 | 21 | ### Using flags 22 | - s Source file/dir path, can be a relative or absolute path 23 | - t Types to generate with all its dependencies (comma separated) 24 | - a Annotation tag used to read metadata (default: json) 25 | - o Output file (relative or absolute path) 26 | - p Pool to reuse object (using sync.Pool) 27 | 28 | Examples: 29 | 30 | - Generate `SomeType` type in `/tmp/myproj` go package, write to file `output.go`: 31 | ```sh 32 | gojay -s /tmp/myproj -t SomeType -o output.go 33 | ``` 34 | 35 | - Generate type `SomeType` in file `somegofile.go`, with custom tag `gojay`, write to stdout: 36 | ```sh 37 | gojay -s somegofile.go -a gojay -t SomeType 38 | ``` 39 | 40 | 41 | ## Generator tags 42 | You can add tags to your structs to control: 43 | 44 | - the JSON key 45 | - skip a struct field 46 | - the use of omitempty methods for marshaling 47 | - timeFormat (java style data format) 48 | - timeLayout (golang time layout) 49 | 50 | 51 | ### Example: 52 | ```go 53 | type A struct { 54 | Str string `json:"string"` 55 | StrOmitEmpty string `json:"stringOrEmpty,omitempty"` 56 | Skip string `json:"-"` 57 | StartTime time.Time `json:"startDate" timeFormat:"yyyy-MM-dd HH:mm:ss"` 58 | EndTime *time.Time `json:"endDate" timeLayout:"2006-01-02 15:04:05"` 59 | } 60 | ``` 61 | 62 | -------------------------------------------------------------------------------- /gojay/codegen/field.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | import ( 4 | "fmt" 5 | "github.com/viant/toolbox" 6 | "strings" 7 | ) 8 | 9 | //Field represents a field. 10 | type Field struct { 11 | Key string 12 | Init string 13 | OmitEmpty string 14 | TimeLayout string 15 | NullType string 16 | Name string 17 | Accessor string 18 | Mutator string 19 | Receiver string //alias and type name 20 | Alias string //object alias name 21 | Var string //variable for this field 22 | Type string 23 | RawType string 24 | HelperType string 25 | ComponentType string 26 | RawComponentType string 27 | IsPointerComponent bool 28 | 29 | PointerModifier string //takes field pointer, "&" if field is not a pointer type 30 | DereferenceModifier string //take pointer value, i.e "*" if field has a pointer type 31 | 32 | ComponentPointerModifier string //takes item pointer if needed,i.e 33 | ComponentDereferenceModifier string //de reference value if needed, i.e 34 | ComponentInitModifier string //takes item pointer if type is not a pointer type 35 | ComponentInit string //initialises component type 36 | 37 | DecodingMethod string 38 | EncodingMethod string 39 | PoolName string //pool name associated with this field 40 | ResetDependency string 41 | Reset string 42 | IsAnonymous bool 43 | IsPointer bool 44 | IsSlice bool 45 | 46 | GojayMethod string 47 | } 48 | 49 | //NewField returns a new field 50 | func NewField(owner *Struct, field *toolbox.FieldInfo, fieldType *toolbox.TypeInfo) (*Field, error) { 51 | typeName := normalizeTypeName(field.TypeName) 52 | var result = &Field{ 53 | IsAnonymous: field.IsAnonymous, 54 | Name: field.Name, 55 | RawType: field.TypeName, 56 | IsPointer: field.IsPointer, 57 | Key: getJSONKey(owner.options, field), 58 | Receiver: owner.Alias + " *" + owner.TypeInfo.Name, 59 | Type: typeName, 60 | Mutator: owner.Alias + "." + field.Name, 61 | Accessor: owner.Alias + "." + field.Name, 62 | ComponentType: field.ComponentType, 63 | IsPointerComponent: field.IsPointerComponent, 64 | Var: firstLetterToLowercase(field.Name), 65 | Init: fmt.Sprintf("%v{}", typeName), 66 | TimeLayout: "time.RFC3339", 67 | IsSlice: field.IsSlice, 68 | PoolName: getPoolName(field.TypeName), 69 | Alias: owner.Alias, 70 | Reset: "nil", 71 | } 72 | var err error 73 | if field.IsPointer { 74 | result.DereferenceModifier = "*" 75 | result.Init = "&" + result.Init 76 | } else { 77 | result.PointerModifier = "&" 78 | 79 | } 80 | if field.IsSlice { 81 | result.HelperType = getSliceHelperTypeName(field.ComponentType, field.IsPointerComponent) 82 | result.PoolName = getPoolName(field.ComponentType) 83 | } else if fieldType != nil { 84 | result.HelperType = getSliceHelperTypeName(fieldType.Name, field.IsPointerComponent) 85 | } 86 | 87 | if options := getTagOptions(field.Tag, "timeLayout"); len(options) > 0 { 88 | result.TimeLayout = wrapperIfNeeded(options[0], `"`) 89 | } else if options := getTagOptions(field.Tag, "timeFormat"); len(options) > 0 { 90 | result.TimeLayout = wrapperIfNeeded(toolbox.DateFormatToLayout(options[0]), `"`) 91 | } 92 | if strings.Contains(field.Tag, "omitempty") { 93 | result.OmitEmpty = "OmitEmpty" 94 | } 95 | if strings.Contains(field.Tag, "nullempty") { 96 | result.OmitEmpty = "NullEmpty" 97 | } 98 | 99 | if owner.options.PoolObjects { 100 | if field.IsPointer && !strings.HasSuffix(field.TypeName, ".Time") && !strings.Contains(field.TypeName, "sql.Null") { 101 | poolName := getPoolName(field.TypeName) 102 | result.Init = fmt.Sprintf(`%v.Get().(*%v)`, poolName, field.TypeName) 103 | } 104 | } 105 | 106 | encodingMethod := field.ComponentType 107 | if encodingMethod == "" { 108 | encodingMethod = result.Type 109 | } 110 | result.DecodingMethod = firstLetterToUppercase(encodingMethod) 111 | result.EncodingMethod = firstLetterToUppercase(encodingMethod) 112 | 113 | switch typeName { 114 | case "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64": 115 | result.Reset = "0" 116 | case "float32", "float64": 117 | result.Reset = "0.0" 118 | case "string": 119 | result.Reset = `""` 120 | 121 | case "bool": 122 | result.Reset = "false" 123 | default: 124 | if field.IsSlice && owner.Type(field.ComponentType) != nil { 125 | var itemPointer = "" 126 | if !field.IsPointerComponent { 127 | itemPointer = "&" 128 | } 129 | result.ResetDependency, err = expandFieldTemplate(poolSliceInstanceRelease, struct { 130 | PoolName string 131 | Accessor string 132 | PointerModifier string 133 | }{PoolName: result.PoolName, Accessor: result.Accessor, PointerModifier: itemPointer}) 134 | if err != nil { 135 | return nil, err 136 | } 137 | 138 | } else if field.IsPointer && fieldType != nil { 139 | result.ResetDependency, err = expandFieldTemplate(poolInstanceRelease, struct { 140 | PoolName string 141 | Accessor string 142 | }{PoolName: result.PoolName, Accessor: result.Accessor}) 143 | if err != nil { 144 | return nil, err 145 | } 146 | } 147 | 148 | } 149 | if field.IsSlice || field.IsPointer { 150 | result.Reset = "nil" 151 | } 152 | 153 | if result.IsPointerComponent { 154 | result.ComponentInit = "&" + result.ComponentType + "{}" 155 | result.RawComponentType = "*" + result.ComponentType 156 | 157 | result.ComponentDereferenceModifier = "*" 158 | result.ComponentInitModifier = "&" 159 | 160 | } else { 161 | result.ComponentInit = result.ComponentType + "{}" 162 | result.RawComponentType = result.ComponentType 163 | 164 | result.ComponentPointerModifier = "&" 165 | } 166 | 167 | return result, nil 168 | } 169 | -------------------------------------------------------------------------------- /gojay/codegen/generator_test.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "github.com/viant/toolbox" 6 | "log" 7 | "path" 8 | "testing" 9 | ) 10 | 11 | func TestGenerator_Generate(t *testing.T) { 12 | 13 | parent := path.Join(toolbox.CallerDirectory(3), "test") 14 | 15 | var useCases = []struct { 16 | description string 17 | options *Options 18 | hasError bool 19 | }{ 20 | { 21 | description: "basic struct code generation", 22 | options: &Options{ 23 | Source: path.Join(parent, "basic_struct"), 24 | Types: []string{"Message"}, 25 | Dest: path.Join(parent, "basic_struct", "encoding.go"), 26 | }, 27 | }, 28 | 29 | { 30 | description: "struct with pool code generation", 31 | options: &Options{ 32 | Source: path.Join(parent, "pooled_struct"), 33 | Types: []string{"Message"}, 34 | Dest: path.Join(parent, "pooled_struct", "encoding.go"), 35 | PoolObjects: true, 36 | }, 37 | }, 38 | { 39 | description: "struct with embedded type code generation", 40 | options: &Options{ 41 | Source: path.Join(parent, "embedded_struct"), 42 | Types: []string{"Message"}, 43 | Dest: path.Join(parent, "embedded_struct", "encoding.go"), 44 | PoolObjects: false, 45 | }, 46 | }, 47 | { 48 | description: "struct with json annotation and time/foarmat|layouat generation", 49 | options: &Options{ 50 | Source: path.Join(parent, "annotated_struct"), 51 | Types: []string{"Message"}, 52 | Dest: path.Join(parent, "annotated_struct", "encoding.go"), 53 | PoolObjects: false, 54 | TagName: "json", 55 | }, 56 | }, 57 | } 58 | 59 | for _, useCase := range useCases { 60 | gen := NewGenerator(useCase.options) 61 | err := gen.Generate() 62 | if useCase.hasError { 63 | assert.NotNil(t, err, useCase.description) 64 | continue 65 | } 66 | if !assert.Nil(t, err, useCase.description) { 67 | log.Fatal(err) 68 | continue 69 | } 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /gojay/codegen/helper.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | import ( 4 | "github.com/viant/toolbox" 5 | "reflect" 6 | "strings" 7 | "sort" 8 | ) 9 | 10 | func firstLetterToUppercase(text string) string { 11 | return strings.ToUpper(string(text[0:1])) + string(text[1:]) 12 | } 13 | 14 | func firstLetterToLowercase(text string) string { 15 | return strings.ToLower(string(text[0:1])) + string(text[1:]) 16 | } 17 | 18 | func extractReceiverAlias(structType string) string { 19 | var result = string(structType[0]) 20 | for i := len(structType) - 1; i > 0; i-- { 21 | aChar := string(structType[i]) 22 | lowerChar := strings.ToLower(aChar) 23 | if lowerChar != aChar { 24 | result = lowerChar 25 | break 26 | } 27 | } 28 | return strings.ToLower(result) 29 | } 30 | 31 | func getTagOptions(tag, key string) []string { 32 | if tag == "" { 33 | return nil 34 | } 35 | var structTag = reflect.StructTag(strings.Replace(tag, "`", "", len(tag))) 36 | options, ok := structTag.Lookup(key) 37 | if !ok { 38 | return nil 39 | } 40 | return strings.Split(options, ",") 41 | } 42 | 43 | func getSliceHelperTypeName(typeName string, isPointer bool) string { 44 | if typeName == "" { 45 | return "" 46 | } 47 | 48 | var pluralName = firstLetterToUppercase(typeName) + "s" 49 | if isPointer { 50 | pluralName += "Ptr" 51 | } 52 | return strings.Replace(pluralName, ".", "", -1) 53 | } 54 | 55 | func isSkipable(options *Options, field *toolbox.FieldInfo) bool { 56 | if options := getTagOptions(field.Tag, options.TagName); len(options) > 0 { 57 | for _, candidate := range options { 58 | if candidate == "-" { 59 | return true 60 | } 61 | } 62 | } 63 | return false 64 | } 65 | 66 | func wrapperIfNeeded(text, wrappingChar string) string { 67 | if strings.HasPrefix(text, wrappingChar) { 68 | return text 69 | } 70 | return wrappingChar + text + wrappingChar 71 | } 72 | 73 | func getPoolName(typeName string) string { 74 | typeName = strings.Replace(typeName, "*", "", 1) 75 | return strings.Replace(typeName+"Pool", ".", "", -1) 76 | } 77 | 78 | func getJSONKey(options *Options, field *toolbox.FieldInfo) string { 79 | var key = field.Name 80 | if field.Tag != "" { 81 | if options := getTagOptions(field.Tag, options.TagName); len(options) > 0 { 82 | key = options[0] 83 | } 84 | } 85 | return key 86 | } 87 | 88 | func normalizeTypeName(typeName string) string { 89 | return strings.Replace(typeName, "*", "", strings.Count(typeName, "*")) 90 | } 91 | 92 | func sortedKeys(m map[string]string) ([]string) { 93 | keys := make([]string, len(m)) 94 | i := 0 95 | for k := range m { 96 | keys[i] = k 97 | i++ 98 | } 99 | sort.Strings(keys) 100 | return keys 101 | } -------------------------------------------------------------------------------- /gojay/codegen/options.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | import ( 4 | "flag" 5 | "strings" 6 | 7 | "github.com/go-errors/errors" 8 | "github.com/viant/toolbox" 9 | "github.com/viant/toolbox/url" 10 | ) 11 | 12 | type Options struct { 13 | Source string 14 | Dest string 15 | Types []string 16 | PoolObjects bool 17 | TagName string 18 | Pkg string 19 | } 20 | 21 | func (o *Options) Validate() error { 22 | if o.Source == "" { 23 | return errors.New("Source was empty") 24 | } 25 | if len(o.Types) == 0 { 26 | return errors.New("Types was empty") 27 | } 28 | return nil 29 | } 30 | 31 | const ( 32 | optionKeySource = "s" 33 | optionKeyDest = "o" 34 | optionKeyTypes = "t" 35 | optionKeyTagName = "a" 36 | optionKeyPoolObjects = "p" 37 | optionKeyPkg = "pkg" 38 | ) 39 | 40 | //NewOptionsWithFlagSet creates a new options for the supplide flagset 41 | func NewOptionsWithFlagSet(set *flag.FlagSet) *Options { 42 | toolbox.Dump(set) 43 | 44 | var result = &Options{} 45 | result.Dest = set.Lookup(optionKeyDest).Value.String() 46 | result.Source = set.Lookup(optionKeySource).Value.String() 47 | result.PoolObjects = toolbox.AsBoolean(set.Lookup(optionKeyPoolObjects).Value.String()) 48 | result.TagName = set.Lookup(optionKeyTagName).Value.String() 49 | result.Types = strings.Split(set.Lookup(optionKeyTypes).Value.String(), ",") 50 | result.Pkg = set.Lookup(optionKeyPkg).Value.String() 51 | if result.Source == "" { 52 | result.Source = url.NewResource(".").ParsedURL.Path 53 | } 54 | return result 55 | } 56 | -------------------------------------------------------------------------------- /gojay/codegen/template_test.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func Test_ExpandTemplate(t *testing.T) { 9 | var dictionary = map[int]string{ 10 | 1: `type {{.TypeName}} {{.SourceTypeName}}`, 11 | } 12 | expaded, err := expandTemplate("test", dictionary, 1, struct { 13 | TypeName string 14 | SourceTypeName string 15 | }{"A", "B"}) 16 | 17 | assert.Nil(t, err) 18 | assert.Equal(t, "type A B", expaded) 19 | } 20 | -------------------------------------------------------------------------------- /gojay/codegen/test/annotated_struct/encoding_test.go: -------------------------------------------------------------------------------- 1 | package annotated_struct 2 | 3 | import ( 4 | "bytes" 5 | "database/sql" 6 | "log" 7 | "testing" 8 | 9 | "github.com/francoispqt/gojay" 10 | "github.com/stretchr/testify/assert" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | var isTrue = true 15 | var msg = &Message{ 16 | Id: 1022, 17 | Name: "name acc", 18 | Price: 13.3, 19 | Ints: []int{1, 2, 5}, 20 | Floats: []float32{2.3, 4.6, 7.4}, 21 | SubMessageX: &SubMessage{ 22 | Id: 102, 23 | Description: "abcd", 24 | }, 25 | MessagesX: []*SubMessage{ 26 | &SubMessage{ 27 | Id: 2102, 28 | Description: "abce", 29 | }, 30 | }, 31 | SubMessageY: SubMessage{ 32 | Id: 3102, 33 | Description: "abcf", 34 | }, 35 | MessagesY: []SubMessage{ 36 | SubMessage{ 37 | Id: 5102, 38 | Description: "abcg", 39 | }, 40 | SubMessage{ 41 | Id: 5106, 42 | Description: "abcgg", 43 | }, 44 | }, 45 | IsTrue: &isTrue, 46 | Payload: []byte(`"123"`), 47 | SQLNullString: &sql.NullString{ 48 | String: "test", 49 | Valid: true, 50 | }, 51 | } 52 | 53 | var jsonData = `{ 54 | "id": 1022, 55 | "name": "name acc", 56 | "price": 13.3, 57 | "ints": [ 58 | 1, 59 | 2, 60 | 5 61 | ], 62 | "floats": [ 63 | 2.3, 64 | 4.6, 65 | 7.4 66 | ], 67 | "subMessageX": { 68 | "id": 102, 69 | "description": "abcd", 70 | "startDate": "0001-01-01 00:00:00" 71 | }, 72 | "messagesX": [ 73 | { 74 | "id": 2102, 75 | "description": "abce", 76 | "startDate": "0001-01-01 00:00:00" 77 | } 78 | ], 79 | "SubMessageY": { 80 | "id": 3102, 81 | "description": "abcf", 82 | "startDate": "0001-01-01 00:00:00" 83 | }, 84 | "MessagesY": [ 85 | { 86 | "id": 5102, 87 | "description": "abcg", 88 | "startDate": "0001-01-01 00:00:00" 89 | }, 90 | { 91 | "id": 5106, 92 | "description": "abcgg", 93 | "startDate": "0001-01-01 00:00:00" 94 | } 95 | ], 96 | "enabled": true, 97 | "data": "123", 98 | "sqlNullString": "test" 99 | }` 100 | 101 | func TestMessage_Unmarshal(t *testing.T) { 102 | 103 | var err error 104 | var data = []byte(jsonData) 105 | message := &Message{} 106 | err = gojay.UnmarshalJSONObject(data, message) 107 | if !assert.Nil(t, err) { 108 | log.Fatal(err) 109 | } 110 | require.Equal( 111 | t, 112 | msg, 113 | message, 114 | ) 115 | } 116 | 117 | func TestMessage_Marshal(t *testing.T) { 118 | var err error 119 | var writer = new(bytes.Buffer) 120 | encoder := gojay.NewEncoder(writer) 121 | err = encoder.Encode(msg) 122 | assert.Nil(t, err) 123 | var JSON = writer.String() 124 | require.JSONEq(t, jsonData, JSON) 125 | 126 | } 127 | -------------------------------------------------------------------------------- /gojay/codegen/test/annotated_struct/message.go: -------------------------------------------------------------------------------- 1 | package annotated_struct 2 | 3 | import "database/sql" 4 | 5 | type Payload []byte 6 | 7 | type Message struct { 8 | Id int `json:"id"` 9 | Name string `json:"name"` 10 | Price float64 `json:"price"` 11 | Ints []int `json:"ints"` 12 | Floats []float32 `json:"floats"` 13 | SubMessageX *SubMessage `json:"subMessageX"` 14 | MessagesX []*SubMessage `json:"messagesX"` 15 | SubMessageY SubMessage 16 | MessagesY []SubMessage 17 | IsTrue *bool `json:"enabled"` 18 | Payload Payload `json:"data"` 19 | Ignore string `json:"-"` 20 | SQLNullString *sql.NullString `json:"sqlNullString"` 21 | } 22 | -------------------------------------------------------------------------------- /gojay/codegen/test/annotated_struct/sub_message.go: -------------------------------------------------------------------------------- 1 | package annotated_struct 2 | 3 | import "time" 4 | 5 | type SubMessage struct { 6 | Id int `json:"id"` 7 | Description string `json:"description"` 8 | StartTime time.Time `json:"startDate" timeFormat:"yyyy-MM-dd HH:mm:ss"` 9 | EndTime *time.Time `json:"endDate" timeLayout:"2006-01-02 15:04:05"` 10 | } 11 | -------------------------------------------------------------------------------- /gojay/codegen/test/basic_struct/encoding_test.go: -------------------------------------------------------------------------------- 1 | package basic_struct 2 | 3 | import ( 4 | "bytes" 5 | "database/sql" 6 | "testing" 7 | 8 | "github.com/francoispqt/gojay" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | var isTrue = true 13 | var msg = &Message{ 14 | Id: 1022, 15 | Name: "name acc", 16 | Price: 13.3, 17 | Ints: []int{1, 2, 5}, 18 | Floats: []float32{2.3, 4.6, 7.4}, 19 | SubMessageX: &SubMessage{ 20 | Id: 102, 21 | Description: "abcd", 22 | }, 23 | MessagesX: []*SubMessage{ 24 | &SubMessage{ 25 | Id: 2102, 26 | Description: "abce", 27 | }, 28 | }, 29 | SubMessageY: SubMessage{ 30 | Id: 3102, 31 | Description: "abcf", 32 | }, 33 | MessagesY: []SubMessage{ 34 | SubMessage{ 35 | Id: 5102, 36 | Description: "abcg", 37 | }, 38 | SubMessage{ 39 | Id: 5106, 40 | Description: "abcgg", 41 | }, 42 | }, 43 | IsTrue: &isTrue, 44 | Payload: []byte(`"123"`), 45 | SQLNullString: &sql.NullString{ 46 | String: "test", 47 | Valid: true, 48 | }, 49 | } 50 | 51 | var jsonData = `{ 52 | "Id": 1022, 53 | "Name": "name acc", 54 | "Price": 13.3, 55 | "Ints": [ 56 | 1, 57 | 2, 58 | 5 59 | ], 60 | "Floats": [ 61 | 2.3, 62 | 4.6, 63 | 7.4 64 | ], 65 | "SubMessageX": { 66 | "Id": 102, 67 | "Description": "abcd", 68 | "StartTime": "0001-01-01T00:00:00Z" 69 | }, 70 | "MessagesX": [ 71 | { 72 | "Id": 2102, 73 | "Description": "abce", 74 | "StartTime": "0001-01-01T00:00:00Z" 75 | } 76 | ], 77 | "SubMessageY": { 78 | "Id": 3102, 79 | "Description": "abcf", 80 | "StartTime": "0001-01-01T00:00:00Z" 81 | }, 82 | "MessagesY": [ 83 | { 84 | "Id": 5102, 85 | "Description": "abcg", 86 | "StartTime": "0001-01-01T00:00:00Z" 87 | }, 88 | { 89 | "Id": 5106, 90 | "Description": "abcgg", 91 | "StartTime": "0001-01-01T00:00:00Z" 92 | } 93 | ], 94 | "IsTrue": true, 95 | "Payload": "123", 96 | "SQLNullString": "test" 97 | }` 98 | 99 | func TestMessage_Unmarshal(t *testing.T) { 100 | var err error 101 | var data = []byte(jsonData) 102 | message := &Message{} 103 | err = gojay.UnmarshalJSONObject(data, message) 104 | require.Nil(t, err) 105 | require.Equal(t, msg, message) 106 | } 107 | 108 | func TestMessage_Marshal(t *testing.T) { 109 | var writer = new(bytes.Buffer) 110 | 111 | encoder := gojay.NewEncoder(writer) 112 | var err = encoder.Encode(msg) 113 | 114 | require.Nil(t, err) 115 | var JSON = writer.String() 116 | 117 | require.JSONEq(t, jsonData, JSON) 118 | } 119 | -------------------------------------------------------------------------------- /gojay/codegen/test/basic_struct/message.go: -------------------------------------------------------------------------------- 1 | package basic_struct 2 | 3 | import "database/sql" 4 | 5 | type Message struct { 6 | Id int 7 | Name string 8 | Price float64 9 | Ints []int 10 | Floats []float32 11 | SubMessageX *SubMessage 12 | MessagesX []*SubMessage 13 | SubMessageY SubMessage 14 | MessagesY []SubMessage 15 | IsTrue *bool 16 | Payload []byte 17 | SQLNullString *sql.NullString 18 | } 19 | -------------------------------------------------------------------------------- /gojay/codegen/test/basic_struct/sub_message.go: -------------------------------------------------------------------------------- 1 | package basic_struct 2 | 3 | import "time" 4 | 5 | type SubMessage struct { 6 | Id int 7 | Description string 8 | StartTime time.Time 9 | EndTime *time.Time 10 | } 11 | -------------------------------------------------------------------------------- /gojay/codegen/test/embedded_struct/encoding_test.go: -------------------------------------------------------------------------------- 1 | package embedded_struct 2 | 3 | import ( 4 | "bytes" 5 | "github.com/francoispqt/gojay" 6 | "github.com/stretchr/testify/assert" 7 | "github.com/viant/assertly" 8 | "testing" 9 | ) 10 | 11 | func TestMessage_Unmarshal(t *testing.T) { 12 | 13 | input := `{ 14 | "Id": 1022, 15 | "Name": "name acc", 16 | "Description": "abcd", 17 | "Price": 13.3, 18 | "Ints": [ 19 | 1, 20 | 2, 21 | 5 22 | ], 23 | "Floats": [ 24 | 2.3, 25 | 4.6, 26 | 7.4 27 | ], 28 | "MessagesX": [ 29 | { 30 | "Description": "abce" 31 | } 32 | ], 33 | "SubMessageY": { 34 | "Description": "abcf" 35 | }, 36 | "MessagesY": [ 37 | { 38 | "Description": "abcg" 39 | }, 40 | { 41 | "Description": "abcgg" 42 | } 43 | ], 44 | "IsTrue": true, 45 | "Payload": "" 46 | }` 47 | 48 | var err error 49 | var data = []byte(input) 50 | message := &Message{} 51 | err = gojay.UnmarshalJSONObject(data, message) 52 | assert.Nil(t, err) 53 | assertly.AssertValues(t, input, message) 54 | } 55 | 56 | func TestMessage_Marshal(t *testing.T) { 57 | 58 | input := `{ 59 | "Id": 1022, 60 | "Name": "name acc", 61 | "Description": "abcd", 62 | "Price": 13.3, 63 | "Ints": [ 64 | 1, 65 | 2, 66 | 5 67 | ], 68 | "Floats": [ 69 | 2.3, 70 | 4.6, 71 | 7.4 72 | ], 73 | "MessagesX": [ 74 | { 75 | "Description": "abce" 76 | } 77 | ], 78 | "SubMessageY": { 79 | "Description": "abcf" 80 | }, 81 | "MessagesY": [ 82 | { 83 | "Description": "abcg" 84 | }, 85 | { 86 | "Description": "abcgg" 87 | } 88 | ], 89 | "IsTrue": true, 90 | "Payload": "" 91 | }` 92 | 93 | var err error 94 | var data = []byte(input) 95 | message := &Message{} 96 | err = gojay.UnmarshalJSONObject(data, message) 97 | assert.Nil(t, err) 98 | assertly.AssertValues(t, input, message) 99 | var writer = new(bytes.Buffer) 100 | 101 | encoder := gojay.NewEncoder(writer) 102 | err = encoder.Encode(message) 103 | assert.Nil(t, err) 104 | var JSON = writer.String() 105 | assertly.AssertValues(t, input, JSON) 106 | } 107 | -------------------------------------------------------------------------------- /gojay/codegen/test/embedded_struct/message.go: -------------------------------------------------------------------------------- 1 | package embedded_struct 2 | 3 | type BaseId struct { 4 | Id int 5 | Name string 6 | } 7 | 8 | type Message struct { 9 | *BaseId 10 | SubMessage 11 | Price float64 12 | Ints []int 13 | Floats []float64 14 | SubMessageX *SubMessage 15 | MessagesX []*SubMessage 16 | SubMessageY SubMessage 17 | MessagesY []SubMessage 18 | IsTrue *bool 19 | Payload []byte 20 | } 21 | -------------------------------------------------------------------------------- /gojay/codegen/test/embedded_struct/sub_message.go: -------------------------------------------------------------------------------- 1 | package embedded_struct 2 | 3 | import "time" 4 | 5 | type SubMessage struct { 6 | Description string 7 | StartTime time.Time 8 | EndTime *time.Time 9 | } 10 | -------------------------------------------------------------------------------- /gojay/codegen/test/pooled_struct/encoding_test.go: -------------------------------------------------------------------------------- 1 | package pooled_struct 2 | 3 | import ( 4 | "bytes" 5 | "github.com/francoispqt/gojay" 6 | "github.com/stretchr/testify/assert" 7 | "github.com/viant/assertly" 8 | "testing" 9 | ) 10 | 11 | func TestMessage_Unmarshal(t *testing.T) { 12 | 13 | input := `{ 14 | "Id": 1022, 15 | "Name": "name acc", 16 | "Price": 13.3, 17 | "Ints": [ 18 | 1, 19 | 2, 20 | 5 21 | ], 22 | "Floats": [ 23 | 2.3, 24 | 4.6, 25 | 7.4 26 | ], 27 | "SubMessageX": { 28 | "Id": 102, 29 | "Description": "abcd" 30 | }, 31 | "MessagesX": [ 32 | { 33 | "Id": 2102, 34 | "Description": "abce" 35 | } 36 | ], 37 | "SubMessageY": { 38 | "Id": 3102, 39 | "Description": "abcf" 40 | }, 41 | "MessagesY": [ 42 | { 43 | "Id": 5102, 44 | "Description": "abcg" 45 | }, 46 | { 47 | "Id": 5106, 48 | "Description": "abcgg" 49 | } 50 | ], 51 | "IsTrue": true, 52 | "Payload": "" 53 | }` 54 | 55 | var data = []byte(input) 56 | message := MessagePool.Get().(*Message) 57 | err := gojay.UnmarshalJSONObject(data, message) 58 | assert.Nil(t, err) 59 | message.Reset() 60 | MessagePool.Put(message) 61 | 62 | } 63 | 64 | func TestMessage_Marshal(t *testing.T) { 65 | 66 | input := `{ 67 | "Id": 1022, 68 | "Name": "name acc", 69 | "Price": 13.3, 70 | "Ints": [ 71 | 1, 72 | 2, 73 | 5 74 | ], 75 | "Floats": [ 76 | 2.3, 77 | 4.6, 78 | 7.4 79 | ], 80 | "SubMessageX": { 81 | "Id": 102, 82 | "Description": "abcd" 83 | }, 84 | "MessagesX": [ 85 | { 86 | "Id": 2102, 87 | "Description": "abce" 88 | } 89 | ], 90 | "SubMessageY": { 91 | "Id": 3102, 92 | "Description": "abcf" 93 | }, 94 | "MessagesY": [ 95 | { 96 | "Id": 5102, 97 | "Description": "abcg" 98 | }, 99 | { 100 | "Id": 5106, 101 | "Description": "abcgg" 102 | } 103 | ], 104 | "IsTrue": true, 105 | "Payload": "" 106 | }` 107 | 108 | var data = []byte(input) 109 | message := MessagePool.Get().(*Message) 110 | err := gojay.UnmarshalJSONObject(data, message) 111 | assert.Nil(t, err) 112 | defer func() { 113 | message.Reset() 114 | MessagePool.Put(message) 115 | 116 | }() 117 | var writer = new(bytes.Buffer) 118 | encoder := gojay.NewEncoder(writer) 119 | err = encoder.Encode(message) 120 | assert.Nil(t, err) 121 | var JSON = writer.String() 122 | assertly.AssertValues(t, input, JSON) 123 | } 124 | -------------------------------------------------------------------------------- /gojay/codegen/test/pooled_struct/message.go: -------------------------------------------------------------------------------- 1 | package pooled_struct 2 | 3 | type Message struct { 4 | Id int 5 | Name string 6 | Price float64 7 | Ints []int 8 | Floats []float64 9 | SubMessageX *SubMessage 10 | MessagesX []*SubMessage 11 | SubMessageY SubMessage 12 | MessagesY []SubMessage 13 | IsTrue *bool 14 | Payload []byte 15 | } 16 | -------------------------------------------------------------------------------- /gojay/codegen/test/pooled_struct/sub_message.go: -------------------------------------------------------------------------------- 1 | package pooled_struct 2 | 3 | import "time" 4 | 5 | type SubMessage struct { 6 | Id int 7 | Description string 8 | StartTime time.Time 9 | EndTime *time.Time 10 | } 11 | -------------------------------------------------------------------------------- /gojay/gojay.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "github.com/francoispqt/gojay/gojay/codegen" 6 | "log" 7 | ) 8 | 9 | var pkg = flag.String("pkg", "", "the package name of the generated file") 10 | var dst = flag.String("o", "", "destination file to output generated code") 11 | var src = flag.String("s", "", "source dir or file (absolute or relative path)") 12 | var types = flag.String("t", "", "types to generate") 13 | var annotation = flag.String("a", "json", "annotation tag (default json)") 14 | var poolObjects = flag.String("p", "", "generate code to reuse objects using sync.Pool") 15 | 16 | func main() { 17 | flag.Parse() 18 | options := codegen.NewOptionsWithFlagSet(flag.CommandLine) 19 | gen := codegen.NewGenerator(options) 20 | if err := gen.Generate(); err != nil { 21 | log.Fatal(err) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /gojay_example_test.go: -------------------------------------------------------------------------------- 1 | package gojay_test 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "strings" 8 | 9 | "github.com/francoispqt/gojay" 10 | ) 11 | 12 | type User struct { 13 | ID int 14 | Name string 15 | Email string 16 | } 17 | 18 | func (u *User) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { 19 | switch k { 20 | case "id": 21 | return dec.Int(&u.ID) 22 | case "name": 23 | return dec.String(&u.Name) 24 | case "email": 25 | return dec.String(&u.Email) 26 | } 27 | return nil 28 | } 29 | 30 | func (u *User) NKeys() int { 31 | return 3 32 | } 33 | 34 | func (u *User) MarshalJSONObject(enc *gojay.Encoder) { 35 | enc.IntKey("id", u.ID) 36 | enc.StringKey("name", u.Name) 37 | enc.StringKey("email", u.Email) 38 | } 39 | 40 | func (u *User) IsNil() bool { 41 | return u == nil 42 | } 43 | 44 | func Example_decodeEncode() { 45 | reader := strings.NewReader(`{ 46 | "id": 1, 47 | "name": "John Doe", 48 | "email": "john.doe@email.com" 49 | }`) 50 | dec := gojay.BorrowDecoder(reader) 51 | defer dec.Release() 52 | 53 | u := &User{} 54 | err := dec.Decode(u) 55 | if err != nil { 56 | log.Fatal(err) 57 | } 58 | 59 | enc := gojay.BorrowEncoder(os.Stdout) 60 | err = enc.Encode(u) 61 | if err != nil { 62 | log.Fatal(err) 63 | } 64 | 65 | fmt.Printf("\nUser ID: %d\nName: %s\nEmail: %s\n", 66 | u.ID, u.Name, u.Email) 67 | 68 | // Output: 69 | // {"id":1,"name":"John Doe","email":"john.doe@email.com"} 70 | // User ID: 1 71 | // Name: John Doe 72 | // Email: john.doe@email.com 73 | } 74 | --------------------------------------------------------------------------------