Skip to content

Commit

Permalink
Merge pull request #12 from neilotoole/refactor-examples
Browse files Browse the repository at this point in the history
Benchmarks, examples, README updates
  • Loading branch information
neilotoole authored Oct 4, 2021
2 parents 31e7295 + 49873b3 commit 776b94f
Show file tree
Hide file tree
Showing 9 changed files with 384 additions and 212 deletions.
153 changes: 138 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@
# jsoncolor

Package `neilotoole/jsoncolor` is a drop-in replacement for `encoding/json`
that can output colorized JSON.
that outputs colorized JSON.

Why? Well, `jq` colorizes its output by default. And at the time this package was
created, I was not aware of any other JSON colorization package that performed
colorization inline in the encoder.

From the example [`jc`](./cmd/jc) app:

![jsoncolor-output](https://github.com/neilotoole/jsoncolor/wiki/images/jsoncolor-example-output2.png)

Expand All @@ -33,12 +39,22 @@ import (

func main() {
var enc *json.Encoder


// Note: this check will fail if running inside Goland (and
// other IDEs?) as IsColorTerminal will return false.
if json.IsColorTerminal(os.Stdout) {
// Safe to use color
out := colorable.NewColorable(os.Stdout) // needed for Windows
enc = json.NewEncoder(out)
enc.SetColors(json.DefaultColors())

// DefaultColors are similar to jq
clrs := json.DefaultColors()

// Change some values, just for fun
clrs.Bool = json.Color("\x1b[36m") // Change the bool color
clrs.String = json.Color{} // Disable the string color

enc.SetColors(clrs)
} else {
// Can't use color; but the encoder will still work
enc = json.NewEncoder(os.Stdout)
Expand All @@ -58,9 +74,83 @@ func main() {
```



### Configuration

To enable colorization, invoke `enc.SetColors`.

The `jsoncolor.Colors` struct holds color config. The zero value
and `nil` are both safe for use (resulting in no colorization).

The `DefaultColors` func returns a `Colors` struct that produces results
similar to `jq`:

```go
// DefaultColors returns the default Colors configuration.
// These colors largely follow jq's default colorization,
// with some deviation.
func DefaultColors() *Colors {
return &Colors{
Null: Color("\x1b[2m"),
Bool: Color("\x1b[1m"),
Number: Color("\x1b[36m"),
String: Color("\x1b[32m"),
Key: Color("\x1b[34;1m"),
Bytes: Color("\x1b[2m"),
Time: Color("\x1b[32;2m"),
Punc: Color{}, // No colorization
}
}
```

As seen above, use the `Color` zero value (`Color{}`) to
disable colorization for that JSON element.


### Helper for `fatih/color`

It can be inconvenient to use terminal codes, e.g. `json.Color("\x1b[36m")`.
A helper package provides an adapter for the [`fatih/color`](https://github.com/fatih/color) package.

```go
// import "github.com/neilotoole/jsoncolor/helper/fatihcolor"
// import "github.com/fatih/color"

out := colorable.NewColorable(os.Stdout) // needed for Windows
enc = json.NewEncoder(out)

fclrs := fatihcolor.DefaultColors()
// Change some values, just for fun
fclrs.Number = color.New(color.FgBlue)
fclrs.String = color.New(color.FgCyan)

clrs := fatihcolor.ToCoreColors(fclrs)
enc.SetColors(clrs)
```

### Drop-in for `encoding/json`

This package is a full drop-in for stdlib `encoding/json`
(thanks to the `segmentio/encoding/json` pkg being a full drop-in).

To drop-in, just use an import alias:

```go
import json "github.com/neilotoole/jsoncolor"
```

### History

This package is an extract of [`sq`](https://github.com/neilotoole/sq)'s JSON encoding
package, which itself is a fork of the [`segment.io/encoding`](https://github.com/segmentio/encoding) JSON
encoding package (aka `segmentj`).

Note that the original `jsoncolor` codebase was forked from Segment's codebase at `v0.1.14`, so
the codebases are quite of out sync by now.

## Example app: `jc`

See `./cmd/jc` for a trivial CLI implementation that can accept JSON input,
See [`./cmd/jc`](.cmd/jc) for a trivial CLI implementation that can accept JSON input,
and output that JSON in color.

```shell
Expand All @@ -69,27 +159,60 @@ go install ./cmd/jc
cat ./testdata/sakila_actor.json | jc
```

## Benchmarks

### History
The package contains `golang_bench_test.go`, which is inherited from `segmentj`.
The test output below is from `BenchmarkEncode`, which benchmarks the following:

This package is an extract of [`sq`](https://github.com/neilotoole/sq)'s JSON encoding
package, which itself is a fork of the [`segment.io/encoding`](https://github.com/segmentio/encoding) JSON
encoding package.
- Stdlib `encoding/json`: Go 1.16
- (`segmentj`)[https://github.com/segmentio/encoding]: `v0.1.14`, which was when `jsoncolor` was forked. The newer `segmentj` code performs even better.
- `neilotoole/jsoncolor`: (this package) `v0.2.0`.
- (`nwidger/jsoncolor`)[https://github.com/nwidger/jsoncolor]: `v0.3.0`, latest at time of benchmarks.

Note that two other Go JSON colorization packages ([`hokaccha/go-prettyjson`](https://github.com/hokaccha/go-prettyjson) and
[`TylerBrock/colorjson`](https://github.com/TylerBrock/colorjson)) are excluded from
these benchmarks because they do not provide a stdlib-compatible `Encoder` impl.

Results (lightly edited for formatting):

```
$ go test -bench=BenchmarkEncode -benchtime="5s"
goarch: amd64
pkg: github.com/neilotoole/jsoncolor
cpu: Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
BenchmarkEncode/stdlib_NoIndent-16 181 33047390 ns/op 8870685 B/op 120022 allocs/op
BenchmarkEncode/stdlib_Indent-16 124 48093178 ns/op 10470366 B/op 120033 allocs/op
BenchmarkEncode/segmentj_NoIndent-16 415 14658699 ns/op 3788911 B/op 10020 allocs/op
BenchmarkEncode/segmentj_Indent-16 195 30628798 ns/op 5404492 B/op 10025 allocs/op
BenchmarkEncode/neilotoole_NoIndent_NoColor-16 362 16522399 ns/op 3789034 B/op 10020 allocs/op
BenchmarkEncode/neilotoole_Indent_NoColor-16 303 20146856 ns/op 5460753 B/op 10021 allocs/op
BenchmarkEncode/neilotoole_NoIndent_Color-16 295 19989420 ns/op 10326019 B/op 10029 allocs/op
BenchmarkEncode/neilotoole_Indent_Color-16 246 24714163 ns/op 11996890 B/op 10030 allocs/op
BenchmarkEncode/nwidger_NoIndent_NoColor-16 10 541107983 ns/op 92934231 B/op 4490210 allocs/op
BenchmarkEncode/nwidger_Indent_NoColor-16 7 798088086 ns/op 117258321 B/op 6290213 allocs/op
BenchmarkEncode/nwidger_indent_NoIndent_Colo-16 10 542002051 ns/op 92935639 B/op 4490224 allocs/op
BenchmarkEncode/nwidger_indent_Indent_Color-16 7 799928353 ns/op 117259195 B/op 6290220 allocs/op
```

As always, take benchmarks with a large grain of salt, as they're based on a (small) synthetic benchmark.
More benchmarks would give a better picture (and note as well that the benchmarked `segmentj` is an older version, `v0.1.14`).

All that having been said, what can we surmise from these particular results?

- `segmentj` performs better than `stdlib` at all encoding tasks.
- `jsoncolor` performs better than `segmentj` for indentation (which makes sense, as indentation is performed inline).
- `jsoncolor` performs better than `stdlib` at all encoding tasks.

Note that the original `jsoncolor` codebase was forked from Segment's package at `v0.1.14`, so
this codebase is quite of out sync by now.
Again, trust these benchmarks at your peril. Create your own benchmarks for your own workload.

### Notes
## Notes

- Given the popularity of the [`fatih/color`](https://github.com/fatih/color) pkg, there is
a helper pkg (`jsoncolor/helper/fatihcolor`) to build `jsoncolor` specs
from `fatih/color`.
- The `.golangci.yml` linter settings have been fiddled with to hush linting issues inherited from
the `segmentio` codebase at the time of forking. Thus, the linter report may not be of great use.
In an ideal world, the `jsoncolor` functionality would be ported to a more recent (and better-linted)
version of the `segementio` codebase.

### Acknowledgments
## Acknowledgments

- [`jq`](https://stedolan.github.io/jq/): sine qua non.
- [`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.
Expand Down
Loading

0 comments on commit 776b94f

Please sign in to comment.