1[![Actions Status](https://github.com/neilotoole/jsoncolor/workflows/Go/badge.svg)](https://github.com/neilotoole/jsoncolor/actions?query=workflow%3AGo)
  2[![Go Report Card](https://goreportcard.com/badge/neilotoole/jsoncolor)](https://goreportcard.com/report/neilotoole/jsoncolor)
  3[![release](https://img.shields.io/badge/release-v0.7.0-green.svg)](https://github.com/neilotoole/jsoncolor/releases/tag/v0.7.0)
  4[![Go Reference](https://pkg.go.dev/badge/github.com/neilotoole/jsoncolor.svg)](https://pkg.go.dev/github.com/neilotoole/jsoncolor)
  5[![license](https://img.shields.io/github/license/neilotoole/jsoncolor)](./LICENSE)
  6
  7# jsoncolor
  8
  9Package `neilotoole/jsoncolor` is a drop-in replacement for stdlib
 10[`encoding/json`](https://pkg.go.dev/encoding/json) that outputs colorized JSON.
 11
 12Why? Well, [`jq`](https://jqlang.github.io/jq/) colorizes its output by default, and color output
 13is desirable for many Go CLIs. This package performs colorization (and indentation) inline
 14in the encoder, and is significantly faster than stdlib at indentation.
 15
 16From the example [`jc`](./cmd/jc/main.go) app:
 17
 18![jsoncolor-output](./splash.png)
 19
 20## Usage
 21
 22Get the package per the normal mechanism (requires Go 1.16+):
 23
 24```shell
 25go get -u github.com/neilotoole/jsoncolor
 26```
 27
 28Then:
 29
 30```go
 31package main
 32
 33import (
 34  "fmt"
 35  "github.com/mattn/go-colorable"
 36  json "github.com/neilotoole/jsoncolor"
 37  "os"
 38)
 39
 40func main() {
 41  var enc *json.Encoder
 42
 43  // Note: this check will fail if running inside Goland (and
 44  // other IDEs?) as IsColorTerminal will return false.
 45  if json.IsColorTerminal(os.Stdout) {
 46    // Safe to use color
 47    out := colorable.NewColorable(os.Stdout) // needed for Windows
 48    enc = json.NewEncoder(out)
 49
 50    // DefaultColors are similar to jq
 51    clrs := json.DefaultColors()
 52
 53    // Change some values, just for fun
 54    clrs.Bool = json.Color("\x1b[36m") // Change the bool color
 55    clrs.String = json.Color{}         // Disable the string color
 56
 57    enc.SetColors(clrs)
 58  } else {
 59    // Can't use color; but the encoder will still work
 60    enc = json.NewEncoder(os.Stdout)
 61  }
 62
 63  m := map[string]interface{}{
 64    "a": 1,
 65    "b": true,
 66    "c": "hello",
 67  }
 68
 69  if err := enc.Encode(m); err != nil {
 70    fmt.Fprintln(os.Stderr, err)
 71    os.Exit(1)
 72  }
 73}
 74```
 75
 76### Configuration
 77
 78To enable colorization, invoke [`enc.SetColors`](https://pkg.go.dev/github.com/neilotoole/jsoncolor#Encoder.SetColors).
 79
 80The [`Colors`](https://pkg.go.dev/github.com/neilotoole/jsoncolor#Colors) struct
 81holds color config. The zero value and `nil` are both safe for use (resulting in no colorization).
 82
 83The [`DefaultColors`](https://pkg.go.dev/github.com/neilotoole/jsoncolor#DefaultColors) func
 84returns a `Colors` struct that produces results similar to `jq`:
 85
 86```go
 87// DefaultColors returns the default Colors configuration.
 88// These colors largely follow jq's default colorization,
 89// with some deviation.
 90func DefaultColors() *Colors {
 91  return &Colors{
 92    Null:   Color("\x1b[2m"),
 93    Bool:   Color("\x1b[1m"),
 94    Number: Color("\x1b[36m"),
 95    String: Color("\x1b[32m"),
 96    Key:    Color("\x1b[34;1m"),
 97    Bytes:  Color("\x1b[2m"),
 98    Time:   Color("\x1b[32;2m"),
 99    Punc:   Color{}, // No colorization
100  }
101}
102```
103
104As seen above, use the `Color` zero value (`Color{}`) to
105disable colorization for that JSON element.
106
107### Helper for `fatih/color`
108
109It can be inconvenient to use terminal codes, e.g. `json.Color("\x1b[36m")`.
110A helper package provides an adapter for [`fatih/color`](https://github.com/fatih/color).
111
112```go
113  // import "github.com/neilotoole/jsoncolor/helper/fatihcolor"
114  // import "github.com/fatih/color"
115  // import "github.com/mattn/go-colorable"
116  
117  out := colorable.NewColorable(os.Stdout) // needed for Windows
118  enc = json.NewEncoder(out)
119  
120  fclrs := fatihcolor.DefaultColors()
121  // Change some values, just for fun
122  fclrs.Number = color.New(color.FgBlue)
123  fclrs.String = color.New(color.FgCyan)
124  
125  clrs := fatihcolor.ToCoreColors(fclrs)
126  enc.SetColors(clrs)
127```
128
129### Drop-in for `encoding/json`
130
131This package is a full drop-in for stdlib [`encoding/json`](https://pkg.go.dev/encoding/json)
132(thanks to the ancestral [`segmentio/encoding/json`](https://pkg.go.dev/github.com/segmentio/encoding/json)
133pkg being a full drop-in).
134
135To drop-in, just use an import alias:
136
137```go
138  import json "github.com/neilotoole/jsoncolor"
139```
140
141## Example app: `jc`
142
143See [`cmd/jc`](cmd/jc/main.go) for a trivial CLI implementation that can accept JSON input,
144and output that JSON in color.
145
146```shell
147# From project root
148$ go install ./cmd/jc
149$ cat ./testdata/sakila_actor.json | jc
150```
151
152## Benchmarks
153
154Note that this package contains [`golang_bench_test.go`](./golang_bench_test.go), which
155is inherited from `segmentj`. But here we're interested in [`benchmark_test.go:BenchmarkEncode`](./benchmark_test.go),
156which benchmarks encoding performance versus other JSON encoder packages.
157The results below benchmark the following:
158
159- Stdlib [`encoding/json`](https://pkg.go.dev/encoding/json) (`go1.17.1`).
160- [`segmentj`](https://github.com/segmentio/encoding): `v0.1.14`, which was when `jsoncolor` was forked. The newer `segmentj` code performs even better.
161- `neilotoole/jsoncolor`: (this package) `v0.6.0`.
162- [`nwidger/jsoncolor`](https://github.com/nwidger/jsoncolor): `v0.3.0`, latest at time of benchmarks.
163
164Note that two other Go JSON colorization packages ([`hokaccha/go-prettyjson`](https://github.com/hokaccha/go-prettyjson) and
165[`TylerBrock/colorjson`](https://github.com/TylerBrock/colorjson)) are excluded from
166these benchmarks because they do not provide a stdlib-compatible `Encoder` impl.
167
168```
169$ go test -bench=BenchmarkEncode -benchtime="5s"
170goarch: amd64
171pkg: github.com/neilotoole/jsoncolor
172cpu: Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
173BenchmarkEncode/stdlib_NoIndent-16                           181          33047390 ns/op         8870685 B/op     120022 allocs/op
174BenchmarkEncode/stdlib_Indent-16                             124          48093178 ns/op        10470366 B/op     120033 allocs/op
175BenchmarkEncode/segmentj_NoIndent-16                         415          14658699 ns/op         3788911 B/op      10020 allocs/op
176BenchmarkEncode/segmentj_Indent-16                           195          30628798 ns/op         5404492 B/op      10025 allocs/op
177BenchmarkEncode/neilotoole_NoIndent_NoColor-16               362          16522399 ns/op         3789034 B/op      10020 allocs/op
178BenchmarkEncode/neilotoole_Indent_NoColor-16                 303          20146856 ns/op         5460753 B/op      10021 allocs/op
179BenchmarkEncode/neilotoole_NoIndent_Color-16                 295          19989420 ns/op        10326019 B/op      10029 allocs/op
180BenchmarkEncode/neilotoole_Indent_Color-16                   246          24714163 ns/op        11996890 B/op      10030 allocs/op
181BenchmarkEncode/nwidger_NoIndent_NoColor-16                   10         541107983 ns/op        92934231 B/op    4490210 allocs/op
182BenchmarkEncode/nwidger_Indent_NoColor-16                      7         798088086 ns/op        117258321 B/op   6290213 allocs/op
183BenchmarkEncode/nwidger_indent_NoIndent_Colo-16               10         542002051 ns/op        92935639 B/op    4490224 allocs/op
184BenchmarkEncode/nwidger_indent_Indent_Color-16                 7         799928353 ns/op        117259195 B/op   6290220 allocs/op
185```
186
187As always, take benchmarks with a large grain of salt, as they're based on a (small) synthetic benchmark.
188More benchmarks would give a better picture (and note as well that the benchmarked `segmentj` is an older version, `v0.1.14`).
189
190All that having been said, what can we surmise from these particular results?
191
192- `segmentj` performs better than `stdlib` at all encoding tasks.
193- `jsoncolor` performs better than `segmentj` for indentation (which makes sense, as indentation is performed inline).
194- `jsoncolor` performs better than `stdlib` at all encoding tasks.
195
196Again, trust these benchmarks at your peril. Create your own benchmarks for your own workload.
197
198## Notes
199
200- The [`.golangci.yml`](./.golangci.yml) linter settings have been fiddled with to hush some
201  linting issues inherited from the `segmentio` codebase at the time of forking. Thus, the linter report
202  may not be of great use. In an ideal world, the `jsoncolor` functionality would be [ported](https://github.com/neilotoole/jsoncolor/issues/15) to a
203  more recent (and better-linted) version of the `segementio` codebase.
204- The `segmentio` encoder (at least as of `v0.1.14`) encodes `time.Duration` as string, while `stdlib` outputs as `int64`.
205  This package follows `stdlib`.
206- The [`Colors.Punc`](https://pkg.go.dev/github.com/neilotoole/jsoncolor#Colors) field controls all
207  punctuation colorization, i.e. `[]{},:"`. It is probably worthwhile to [separate](https://github.com/neilotoole/jsoncolor/issues/16)
208  these out into individually-configurable elements.
209
210<a name="history"></a>
211## CHANGELOG
212
213History: this package is an extract of [`sq`](https://github.com/neilotoole/sq)'s JSON encoding package, which itself is a fork of the
214[`segmentio/encoding`](https://github.com/segmentio/encoding) JSON encoding package. Note that the
215original `sq` JSON encoder was forked from Segment's codebase at `v0.1.14`, so
216the codebases have drifted significantly by now.
217
218### [v0.7.1](https://github.com/neilotoole/jsoncolor/releases/tag/v0.7.1)
219
220- [#27](https://github.com/neilotoole/jsoncolor/pull/27): Improved Windows terminal color support checking.
221
222### [v0.7.0](https://github.com/neilotoole/jsoncolor/releases/tag/v0.7.0)
223
224- [#21](https://github.com/neilotoole/jsoncolor/pull/21): Support for [`encoding.TextMarshaler`](https://pkg.go.dev/encoding#TextMarshaler).
225- [#22](https://github.com/neilotoole/jsoncolor/pull/22): Removed redundant dependencies.
226- [#26](https://github.com/neilotoole/jsoncolor/pull/26): Updated dependencies.
227
228## Acknowledgments
229
230- [`jq`](https://stedolan.github.io/jq/): sine qua non.
231- [`segmentio/encoding`](https://github.com/segmentio/encoding): `jsoncolor` is layered into Segment's JSON encoder. They did the hard work. Much gratitude to that team.
232- [`sq`](https://github.com/neilotoole/sq): `jsoncolor` is effectively an extract of code created specifically for `sq`.
233- [`mattn/go-colorable`](https://github.com/mattn/go-colorable): no project is complete without `mattn` having played a role.
234- [`fatih/color`](https://github.com/fatih/color): the color library.
235- [`@hermannm`](https://github.com/hermannm): for several PRs.
236
237### Related
238
239- [`nwidger/jsoncolor`](https://github.com/nwidger/jsoncolor)
240- [`hokaccha/go-prettyjson`](https://github.com/hokaccha/go-prettyjson)
241- [`TylerBrock/colorjson`](https://github.com/TylerBrock/colorjson)