github.com/wI2L/jettison@v0.7.4/README.md (about) 1 <h1 align="center">Jettison</h1> 2 <p align="center"><img src="images/logo.png" height="275px" width="auto" alt="GoCaptain"></p><p align="center">Jettison is a fast and flexible <strong>JSON</strong> encoder for the Go programming language, inspired by <a href="https://github.com/bet365/jingo">bet365/jingo</a>, with a richer features set, aiming at <strong>100%</strong> compatibility with the standard library.</p> 3 <p align="center"> 4 <a href="https://pkg.go.dev/github.com/wI2L/jettison?tab=doc"><img src="https://img.shields.io/static/v1?label=godev&message=reference&color=00add8&logo=go"></a> 5 <a href="https://goreportcard.com/report/wI2L/jettison"><img src="https://goreportcard.com/badge/github.com/wI2L/fizz"></a> 6 <a href="https://travis-ci.org/wI2L/jettison"><img src="https://travis-ci.org/wI2L/jettison.svg?branch=master"></a> 7 <a href="https://github.com/wI2L/jettison/actions"><img src="https://github.com/wI2L/jettison/workflows/CI/badge.svg"></a> 8 <a href="https://codecov.io/gh/wI2L/jettison"><img src="https://codecov.io/gh/wI2L/jettison/branch/master/graph/badge.svg"/></a> 9 <a href="https://github.com/wI2L/jettison/releases"><img src="https://img.shields.io/github/v/tag/wI2L/jettison?color=blueviolet&label=version&sort=semver"></a> 10 <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg"></a> 11 <a href="https://github.com/avelino/awesome-go"><img src="https://awesome.re/mentioned-badge.svg"></a> 12 <br> 13 </p> 14 15 --- 16 17 ## Installation 18 19 Jettison uses [Go modules](https://github.com/golang/go/wiki/Modules). Releases are tagged according to the _SemVer_ format, prefixed with a `v`, starting from *0.2.0*. You can get the latest release using the following command. 20 21 ```console 22 $ go get github.com/wI2L/jettison@latest 23 ``` 24 25 :warning: From version `v0.7.4`, the packages requires [Go 1.17+](https://golang.org/doc/install) to build, due to the usage of the [new build constraints](https://go.googlesource.com/proposal/+/master/design/draft-gobuild.md). 26 27 ## Key features 28 29 - Fast, see [benchmarks](#benchmarks) 30 - No dynamic memory allocations in hot paths 31 - Behavior identical to the standard library by default 32 - No code generation required 33 - Clear and concise API 34 - Configurable with opt-in functional options 35 - Native support for many standard library types, see [improvements](#improvements) 36 - Custom `AppendMarshaler` interface to avoid allocations 37 - Extensive testsuite that compares its output against `encoding/json` 38 39 ## Overview 40 41 The goal of Jettision is to take up the idea introduced by the **bet365/jingo** package and build a fully-featured JSON encoder around it, that comply with the behavior of the [encoding/json](https://golang.org/pkg/encoding/json/) package. Unlike the latter, Jettison does not use reflection during marshaling, but only once to create the instruction set for a given type ahead of time. The drawback to this approach requires to instantiate an instruction-set once for each type that needs to be marshaled, but that is overcomed with a package cache. 42 43 The package aims to have a behavior similar to that of the standard library for all types encoding and struct tags, meaning that the documentation of the `json.Marshal` [function](https://golang.org/pkg/encoding/json/#Marshal) is applicable for Jettison, with a few exceptions described in this [section](#differences-with-encodingjson). As such, most of the tests compare their output against it to guarantee that. 44 45 ### Implementation details 46 47 The main concept of Jettison consists of using pre-build instructions-set to reduce the cost of using the `reflect` package at runtime. When marshaling a value, a set of _instructions_ is recursively generated for its type, which defines how to iteratively encode it. An _instruction_ is a function or a closure, that have all the information required to read the data from memory using _unsafe_ operations (pointer type conversion, arithmetic...) during the instruction set execution. 48 49 ### Differences with `encoding/json` 50 51 All notable differences with the standard library behavior are listed below. Please note that these might evolve with future versions of the package. 52 53 #### Improvements 54 55 - The `time.Time` and `time.Duration` types are handled natively. For time values, the encoder doesn't invoke `MarshalJSON` or `MarshalText`, but use the `time.AppendFormat` [function](https://golang.org/pkg/time/#Time.AppendFormat) instead, and write the result to the stream. Similarly, for durations, it isn't necessary to implements the `json.Marshaler` or `encoding.TextMarshaler` interfaces on a custom wrapper type, the encoder uses the result of one of the methods `Minutes`, `Seconds`, `Nanoseconds` or `String`, based on the duration [format](https://godoc.org/github.com/wI2L/jettison#DurationFmt) configured. 56 57 - The `sync.Map` type is handled natively. The marshaling behavior is similar to the one of a standard Go `map`. The option `UnsortedMap` can also be used in cunjunction with this type to disable the default keys sort. 58 59 - The `omitnil` field tag's option can be used to specify that a field with a nil pointer should be omitted from the encoding. This option has precedence over the `omitempty` option. 60 61 #### Bugs 62 63 ##### Go1.13 and backward 64 65 - Nil map keys values implementing the `encoding.TextMarshaler` interface are encoded as empty strings, while the `encoding/json` package currently panic because of that. See this [issue](https://github.com/golang/go/issues/33675) for more details.<sup>[1]</sup> 66 67 - Nil struct fields implementing the `encoding.TextMarshaler` interface are encoded as `null`, while the `encoding/json` package currently panic because of that. See this [issue](https://github.com/golang/go/issues/34235) for more details.<sup>[1]</sup> 68 69 <sup>1: The issues mentioned above have had their associated CL merged, and was released with Go 1.14.</sup> 70 71 ## Usage 72 73 ### Basic 74 75 As stated above, the library behave similarly to the `encoding/json` package. You can simply replace the `json.Marshal` function with `jettison.Marshal`, and expect the same output with better performances. 76 77 ```go 78 type X struct { 79 A string `json:"a"` 80 B int64 `json:"b"` 81 } 82 b, err := jettison.Marshal(X{ 83 A: "Loreum", 84 B: 42, 85 }) 86 if err != nil { 87 log.Fatal(err) 88 } 89 os.Stdout.Write(b) 90 ``` 91 ###### Result 92 ```json 93 {"a":"Loreum","b":42} 94 ``` 95 96 ### Advanced 97 98 If more control over the encoding behavior is required, use the `MarshalOpts` function instead. The second parameter is variadic and accept a list of functional opt-in [options](https://godoc.org/github.com/wI2L/jettison#Option) described below: 99 100 | name | description | 101 |:------------------------:| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 102 | **`TimeLayout`** | Defines the layout used to encode `time.Time` values. The layout must be compatible with the [AppendFormat](https://golang.org/pkg/time/#Time.AppendFormat) method. | 103 | **`DurationFormat`** | Defines the format used to encode `time.Duration` values. See the documentation of the `DurationFmt` type for the complete list of formats available. | 104 | **`UnixTime`** | Encode `time.Time` values as JSON numbers representing Unix timestamps, the number of seconds elapsed since *January 1, 1970 UTC*. This option has precedence over `TimeLayout`. | 105 | **`UnsortedMap`** | Disables map keys sort. | 106 | **`ByteArrayAsString`** | Encodes byte arrays as JSON strings rather than JSON arrays. The output is subject to the same escaping rules used for JSON strings, unless the option `NoStringEscaping` is used. | 107 | **`RawByteSlice`** | Disables the *base64* default encoding used for byte slices. | 108 | **`NilMapEmpty`** | Encodes nil Go maps as empty JSON objects rather than `null`. | 109 | **`NilSliceEmpty`** | Encodes nil Go slices as empty JSON arrays rather than `null`. | 110 | **`NoStringEscaping`** | Disables string escaping. `NoHTMLEscaping` and `NoUTF8Coercion` are ignored when this option is used. | 111 | **`NoHTMLEscaping`** | Disables the escaping of special HTML characters such as `&`, `<` and `>` in JSON strings. This is similar to `json.Encoder.SetEscapeHTML(false)`. | 112 | **`NoUTF8Coercion`** | Disables the replacement of invalid bytes with the Unicode replacement rune in JSON strings. | 113 | **`AllowList`** | Sets a whitelist that represents which fields are to be encoded when marshaling a Go struct. | 114 | **`DenyList`** | Sets a blacklist that represents which fields are ignored during the marshaling of a Go struct. | 115 | **`NoCompact`** | Disables the compaction of JSON output produced by `MarshalJSON` method, and `json.RawMessage` values. | 116 | **`NoNumberValidation`** | Disables the validation of `json.Number` values. | 117 | **`WithContext`** | Sets the `context.Context` to be passed to invocations of `AppendJSONContext` methods. | 118 119 Take a look at the [examples](example_test.go) to see these options in action. 120 121 ## Benchmarks 122 123 If you'd like to run the benchmarks yourself, use the following command. 124 125 ```shell 126 go get github.com/cespare/prettybench 127 go test -bench=. | prettybench 128 ``` 129 130 ### Results `-short` 131 132 These benchmarks were run 10x (statistics computed with [benchstat](https://godoc.org/golang.org/x/perf/cmd/benchstat)) on a MacBook Pro 15", with the following specs: 133 ``` 134 OS: macOS Catalina (10.15.7) 135 CPU: 2.6 GHz Intel Core i7 136 Mem: 16GB 137 Go: go version go1.17 darwin/amd64 138 Tag: v0.7.2 139 ``` 140 141 <details><summary>Stats</summary><br><pre> 142 name time/op 143 Simple/standard-8 573ns ± 1% 144 Simple/jsoniter-8 547ns ± 0% 145 Simple/segmentj-8 262ns ± 1% 146 Simple/jettison-8 408ns ± 1% 147 Complex/standard-8 11.7µs ± 0% 148 Complex/jsoniter-8 11.6µs ± 1% 149 Complex/segmentj-8 7.96µs ± 0% 150 Complex/jettison-8 5.90µs ± 1% 151 CodeMarshal/standard-8 6.71ms ± 0% 152 CodeMarshal/jsoniter-8 6.35ms ± 1% 153 CodeMarshal/segmentj-8 4.38ms ± 1% 154 CodeMarshal/jettison-8 5.56ms ± 1% 155 Map/standard-8 1.83µs ± 1% 156 Map/jsoniter-8 1.65µs ± 0% 157 Map/segmentj-8 1.61µs ± 0% 158 Map/jettison-8 772ns ± 1% 159 Map/jettison-nosort-8 507ns ± 1% 160 161 name speed 162 Simple/standard-8 236MB/s ± 1% 163 Simple/jsoniter-8 247MB/s ± 0% 164 Simple/segmentj-8 516MB/s ± 1% 165 Simple/jettison-8 331MB/s ± 1% 166 Complex/standard-8 72.9MB/s ± 0% 167 Complex/jsoniter-8 70.6MB/s ± 0% 168 Complex/segmentj-8 108MB/s ± 0% 169 Complex/jettison-8 144MB/s ± 1% 170 CodeMarshal/standard-8 289MB/s ± 0% 171 CodeMarshal/jsoniter-8 306MB/s ± 1% 172 CodeMarshal/segmentj-8 443MB/s ± 1% 173 CodeMarshal/jettison-8 349MB/s ± 1% 174 Map/standard-8 46.6MB/s ± 1% 175 Map/jsoniter-8 51.5MB/s ± 0% 176 Map/segmentj-8 52.8MB/s ± 0% 177 Map/jettison-8 110MB/s ± 1% 178 Map/jettison-nosort-8 168MB/s ± 1% 179 180 name alloc/op 181 Simple/standard-8 144B ± 0% 182 Simple/jsoniter-8 152B ± 0% 183 Simple/segmentj-8 144B ± 0% 184 Simple/jettison-8 144B ± 0% 185 Complex/standard-8 4.05kB ± 0% 186 Complex/jsoniter-8 3.95kB ± 0% 187 Complex/segmentj-8 2.56kB ± 0% 188 Complex/jettison-8 935B ± 0% 189 CodeMarshal/standard-8 1.97MB ± 0% 190 CodeMarshal/jsoniter-8 2.00MB ± 0% 191 CodeMarshal/segmentj-8 1.98MB ± 2% 192 CodeMarshal/jettison-8 1.98MB ± 2% 193 Map/standard-8 888B ± 0% 194 Map/jsoniter-8 884B ± 0% 195 Map/segmentj-8 576B ± 0% 196 Map/jettison-8 96.0B ± 0% 197 Map/jettison-nosort-8 160B ± 0% 198 199 name allocs/op 200 Simple/standard-8 1.00 ± 0% 201 Simple/jsoniter-8 2.00 ± 0% 202 Simple/segmentj-8 1.00 ± 0% 203 Simple/jettison-8 1.00 ± 0% 204 Complex/standard-8 79.0 ± 0% 205 Complex/jsoniter-8 71.0 ± 0% 206 Complex/segmentj-8 52.0 ± 0% 207 Complex/jettison-8 8.00 ± 0% 208 CodeMarshal/standard-8 1.00 ± 0% 209 CodeMarshal/jsoniter-8 2.00 ± 0% 210 CodeMarshal/segmentj-8 1.00 ± 0% 211 CodeMarshal/jettison-8 1.00 ± 0% 212 Map/standard-8 19.0 ± 0% 213 Map/jsoniter-8 14.0 ± 0% 214 Map/segmentj-8 18.0 ± 0% 215 Map/jettison-8 1.00 ± 0% 216 Map/jettison-nosort-8 2.00 ± 0% 217 </pre></details> 218 219 #### Simple [[source](https://github.com/wI2L/jettison/blob/master/bench_test.go#L50)] 220 221 Basic payload with fields of type `string`, `int` and `bool`. 222 223  224 225 #### Complex [[source](https://github.com/wI2L/jettison/blob/master/bench_test.go#L65)] 226 227 Large payload with a variety of composite Go types, such as `struct`, `map`, `interface`, multi-dimensions `array` and `slice`, with pointer and non-pointer value types. 228 229 Please note that this test is somewhat positively influenced by the performances of map marshaling. 230 231  232 233 #### CodeMarshal [[source](https://github.com/wI2L/jettison/blob/master/bench_test.go#L69)] 234 235 Borrowed from the `encoding/json` tests. See [testdata/code.json.gz](testdata/code.json.gz). 236 237  238 239 #### Map [[source](https://github.com/wI2L/jettison/blob/master/bench_test.go#L75)] 240 241 Simple `map[string]int` with 6 keys. 242 243  244 245 ## Credits 246 247 This library and its design has been inspired by the work of others at **@bet365** and **@segmentio**. 248 See the following projects for reference: 249 - [bet365/jingo](https://github.com/bet365/jingo) 250 - [segmentio/encoding](https://github.com/segmentio/encoding) 251 252 ## License 253 254 Jettison is licensed under the **MIT** license. See the [LICENSE](LICENSE) file. 255 256 This package also uses some portions of code from the Go **encoding/json** package. The associated license can be found in [LICENSE.golang](LICENSE.golang).