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  ![Simple Benchmark Graph](./images/benchmarks/simple.svg)
   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  ![Complex Benchmark Graph](./images/benchmarks/complex.svg)
   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  ![CodeMarshal Benchmark Graph](./images/benchmarks/code-marshal.svg)
   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  ![Map Graph](./images/benchmarks/map.svg)
   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).