golib/colorjson/colorjson.go

169 lines
3.6 KiB
Go

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) 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 any) ([]byte, error) {
buffer := bytes.Buffer{}
f.marshalValue(jsonObj, &buffer, initialDepth)
return buffer.Bytes(), nil
}
func (f *Formatter) marshalMap(m map[string]any, 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)
if f.DisabledColor {
fmt.Fprintf(buf, "\"%s\": ", key)
} else {
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 []any, 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 any, buf *bytes.Buffer, depth int) {
switch v := val.(type) {
case map[string]any:
f.marshalMap(v, buf, depth)
case []any:
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 any) ([]byte, error) {
return NewFormatter().Marshal(jsonObj)
}