From 535bf2f8242771744b4a7f70c2d174b3beb4e622 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Thu, 2 Oct 2025 23:10:11 -0600 Subject: [PATCH] feat(colorsync): hard fork for maintenance --- colorjson/LICENSE | 22 ++++ colorjson/README.md | 65 ++++++++++ colorjson/benchmarks/colorjson_test.go | 39 ++++++ colorjson/benchmarks/go.mod | 15 +++ colorjson/colorjson.go | 171 +++++++++++++++++++++++++ colorjson/examples/indent/main.go | 30 +++++ colorjson/examples/simple/main.go | 26 ++++ colorjson/go.mod | 11 ++ colorjson/go.sum | 11 ++ 9 files changed, 390 insertions(+) create mode 100644 colorjson/LICENSE create mode 100644 colorjson/README.md create mode 100644 colorjson/benchmarks/colorjson_test.go create mode 100644 colorjson/benchmarks/go.mod create mode 100644 colorjson/colorjson.go create mode 100644 colorjson/examples/indent/main.go create mode 100644 colorjson/examples/simple/main.go create mode 100644 colorjson/go.mod create mode 100644 colorjson/go.sum diff --git a/colorjson/LICENSE b/colorjson/LICENSE new file mode 100644 index 0000000..89822f7 --- /dev/null +++ b/colorjson/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2017 Tyler Brock +Copyright (c) 2025 AJ ONeal + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/colorjson/README.md b/colorjson/README.md new file mode 100644 index 0000000..558a991 --- /dev/null +++ b/colorjson/README.md @@ -0,0 +1,65 @@ +# ColorJSON: The Fast Color JSON Marshaller for Go + +![ColorJSON Output](https://i.imgur.com/pLtCXhb.png) + +## What is this? + +This package is based heavily on hokaccha/go-prettyjson but has some noticeable differences: + +- Over twice as fast (recursive descent serialization uses buffer instead of string concatenation) + ``` + BenchmarkColorJSONMarshall-4 500000 2498 ns/op + BenchmarkPrettyJSON-4 200000 6145 ns/op + ``` +- more customizable (ability to have zero indent, print raw json strings, etc...) +- better defaults (less bold colors) + +ColorJSON was built in order to produce fast beautiful colorized JSON output for [Saw](http://github.com/TylerBrock/saw). + +## Installation + +```sh +go get -u github.com/therootcompany/golib/colorjson +``` + +## Usage + +Setup + +```go +import "github.com/therootcompany/golib/colorjson" + +str := `{ + "str": "foo", + "num": 100, + "bool": false, + "null": null, + "array": ["foo", "bar", "baz"], + "obj": { "a": 1, "b": 2 } +}` + +// Create an intersting JSON object to marshal in a pretty format +var obj map[string]interface{} +json.Unmarshal([]byte(str), &obj) +``` + +Vanilla Usage + +```go +s, _ := colorjson.Marshal(obj) +fmt.Println(string(s)) +``` + +Customization (Custom Indent) + +```go +f := colorjson.NewFormatter() +f.Indent = 2 + +s, _ := f.Marshal(v) +fmt.Println(string(s)) +``` + +## License + +This is a fork of for maintenance. diff --git a/colorjson/benchmarks/colorjson_test.go b/colorjson/benchmarks/colorjson_test.go new file mode 100644 index 0000000..e171502 --- /dev/null +++ b/colorjson/benchmarks/colorjson_test.go @@ -0,0 +1,39 @@ +package colorjson_test + +import ( + "testing" + + "github.com/therootcompany/golib/colorjson" + "github.com/hokaccha/go-prettyjson" +) + +func benchmarkMarshall(i int, b *testing.B) { + simpleMap := make(map[string]interface{}) + simpleMap["a"] = 1 + simpleMap["b"] = "bee" + simpleMap["c"] = [3]float64{1, 2, 3} + simpleMap["d"] = [3]string{"one", "two", "three"} + + // run the Fib function b.N times + for n := 0; n < b.N; n++ { + colorjson.Marshal(simpleMap) + } +} + +func benchmarkPrettyJSON(i int, b *testing.B) { + simpleMap := make(map[string]interface{}) + simpleMap["a"] = 1 + simpleMap["b"] = "bee" + simpleMap["c"] = [3]float64{1, 2, 3} + simpleMap["d"] = [3]string{"one", "two", "three"} + + // run the Fib function b.N times + for n := 0; n < b.N; n++ { + prettyjson.Marshal(simpleMap) + } +} + +func BenchmarkMarshall(b *testing.B) { benchmarkMarshall(100, b) } +func BenchmarkMarshall1k(b *testing.B) { benchmarkMarshall(1000, b) } +func BenchmarkPrettyJSON(b *testing.B) { benchmarkPrettyJSON(100, b) } +func BenchmarkPrettyJSON1k(b *testing.B) { benchmarkPrettyJSON(1000, b) } diff --git a/colorjson/benchmarks/go.mod b/colorjson/benchmarks/go.mod new file mode 100644 index 0000000..2b1b3e7 --- /dev/null +++ b/colorjson/benchmarks/go.mod @@ -0,0 +1,15 @@ +module github.com/therootcompany/golib/colorjson/benchmarks + +go 1.18.0 + +require ( + github.com/therootcompany/golib/colorjson v0.0.0-20200706003622-8a50f05110d2 + github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f +) + +require ( + github.com/fatih/color v1.18.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + golang.org/x/sys v0.25.0 // indirect +) diff --git a/colorjson/colorjson.go b/colorjson/colorjson.go new file mode 100644 index 0000000..948247c --- /dev/null +++ b/colorjson/colorjson.go @@ -0,0 +1,171 @@ +package colorjson + +import ( + "bytes" + "encoding/json" + "fmt" + "sort" + "strconv" + "strings" + + "github.com/fatih/color" +) + +const initialDepth = 0 +const valueSep = "," +const null = "null" +const startMap = "{" +const endMap = "}" +const startArray = "[" +const endArray = "]" + +const emptyMap = startMap + endMap +const emptyArray = startArray + endArray + +type Formatter struct { + KeyColor *color.Color + StringColor *color.Color + BoolColor *color.Color + NumberColor *color.Color + NullColor *color.Color + StringMaxLength int + Indent int + DisabledColor bool + RawStrings bool +} + +func NewFormatter() *Formatter { + return &Formatter{ + KeyColor: color.New(color.FgWhite), + StringColor: color.New(color.FgGreen), + BoolColor: color.New(color.FgYellow), + NumberColor: color.New(color.FgCyan), + NullColor: color.New(color.FgMagenta), + StringMaxLength: 0, + DisabledColor: false, + Indent: 0, + RawStrings: false, + } +} + +func (f *Formatter) sprintfColor(c *color.Color, format string, args ...interface{}) string { + if f.DisabledColor || c == nil { + return fmt.Sprintf(format, args...) + } + return c.SprintfFunc()(format, args...) +} + +func (f *Formatter) sprintColor(c *color.Color, s string) string { + if f.DisabledColor || c == nil { + return fmt.Sprint(s) + } + return c.SprintFunc()(s) +} + +func (f *Formatter) writeIndent(buf *bytes.Buffer, depth int) { + buf.WriteString(strings.Repeat(" ", f.Indent*depth)) +} + +func (f *Formatter) writeObjSep(buf *bytes.Buffer) { + if f.Indent != 0 { + buf.WriteByte('\n') + } else { + buf.WriteByte(' ') + } +} + +func (f *Formatter) Marshal(jsonObj interface{}) ([]byte, error) { + buffer := bytes.Buffer{} + f.marshalValue(jsonObj, &buffer, initialDepth) + return buffer.Bytes(), nil +} + +func (f *Formatter) marshalMap(m map[string]interface{}, buf *bytes.Buffer, depth int) { + remaining := len(m) + + if remaining == 0 { + buf.WriteString(emptyMap) + return + } + + keys := make([]string, 0) + for key := range m { + keys = append(keys, key) + } + + sort.Strings(keys) + + buf.WriteString(startMap) + f.writeObjSep(buf) + + for _, key := range keys { + f.writeIndent(buf, depth+1) + buf.WriteString(f.KeyColor.Sprintf("\"%s\": ", key)) + f.marshalValue(m[key], buf, depth+1) + remaining-- + if remaining != 0 { + buf.WriteString(valueSep) + } + f.writeObjSep(buf) + } + f.writeIndent(buf, depth) + buf.WriteString(endMap) +} + +func (f *Formatter) marshalArray(a []interface{}, buf *bytes.Buffer, depth int) { + if len(a) == 0 { + buf.WriteString(emptyArray) + return + } + + buf.WriteString(startArray) + f.writeObjSep(buf) + + for i, v := range a { + f.writeIndent(buf, depth+1) + f.marshalValue(v, buf, depth+1) + if i < len(a)-1 { + buf.WriteString(valueSep) + } + f.writeObjSep(buf) + } + f.writeIndent(buf, depth) + buf.WriteString(endArray) +} + +func (f *Formatter) marshalValue(val interface{}, buf *bytes.Buffer, depth int) { + switch v := val.(type) { + case map[string]interface{}: + f.marshalMap(v, buf, depth) + case []interface{}: + f.marshalArray(v, buf, depth) + case string: + f.marshalString(v, buf) + case float64: + buf.WriteString(f.sprintColor(f.NumberColor, strconv.FormatFloat(v, 'f', -1, 64))) + case bool: + buf.WriteString(f.sprintColor(f.BoolColor, (strconv.FormatBool(v)))) + case nil: + buf.WriteString(f.sprintColor(f.NullColor, null)) + case json.Number: + buf.WriteString(f.sprintColor(f.NumberColor, v.String())) + } +} + +func (f *Formatter) marshalString(str string, buf *bytes.Buffer) { + if !f.RawStrings { + strBytes, _ := json.Marshal(str) + str = string(strBytes) + } + + if f.StringMaxLength != 0 && len(str) >= f.StringMaxLength { + str = fmt.Sprintf("%s...", str[0:f.StringMaxLength]) + } + + buf.WriteString(f.sprintColor(f.StringColor, str)) +} + +// Marshal JSON data with default options +func Marshal(jsonObj interface{}) ([]byte, error) { + return NewFormatter().Marshal(jsonObj) +} diff --git a/colorjson/examples/indent/main.go b/colorjson/examples/indent/main.go new file mode 100644 index 0000000..e954cb1 --- /dev/null +++ b/colorjson/examples/indent/main.go @@ -0,0 +1,30 @@ +package main + +import ( + "encoding/json" + "fmt" + + "github.com/therootcompany/golib/colorjson" +) + +func main() { + str := `{ + "str": "foo", + "num": 100, + "bool": false, + "null": null, + "array": ["foo", "bar", "baz"], + "obj": { "a": 1, "b": 2 } + }` + + var obj map[string]interface{} + json.Unmarshal([]byte(str), &obj) + + // Make a custom formatter with indent set + f := colorjson.NewFormatter() + f.Indent = 4 + + // Marshall the Colorized JSON + s, _ := f.Marshal(obj) + fmt.Println(string(s)) +} diff --git a/colorjson/examples/simple/main.go b/colorjson/examples/simple/main.go new file mode 100644 index 0000000..3853a50 --- /dev/null +++ b/colorjson/examples/simple/main.go @@ -0,0 +1,26 @@ +package main + +import ( + "encoding/json" + "fmt" + + "github.com/therootcompany/golib/colorjson" +) + +func main() { + str := `{ + "str": "foo", + "num": 100, + "bool": false, + "null": null, + "array": ["foo", "bar", "baz"], + "obj": { "a": 1, "b": 2 } + }` + + var obj map[string]interface{} + json.Unmarshal([]byte(str), &obj) + + // Marshall the Colorized JSON + s, _ := colorjson.Marshal(obj) + fmt.Println(string(s)) +} diff --git a/colorjson/go.mod b/colorjson/go.mod new file mode 100644 index 0000000..7f7c83f --- /dev/null +++ b/colorjson/go.mod @@ -0,0 +1,11 @@ +module github.com/therootcompany/golib/colorjson + +go 1.18.0 + +require github.com/fatih/color v1.18.0 + +require ( + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + golang.org/x/sys v0.25.0 // indirect +) diff --git a/colorjson/go.sum b/colorjson/go.sum new file mode 100644 index 0000000..33148a4 --- /dev/null +++ b/colorjson/go.sum @@ -0,0 +1,11 @@ +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=