core/ginhelper/parse.go
package ginhelper
import (
"bytes"
"encoding/json"
"fmt"
"sort"
"strconv"
"strings"
)
// StringMaxLength is the maximum length of a string to display.
var StringMaxLength = 0
// Newline is the string to use for newlines.
var Newline = "\n"
// Indent is the number of spaces to indent.
var Indent = 4
// BeautifyJSONBytes beautifies json bytes.
func BeautifyJSONBytes(data []byte, hiddenFields []string) ([]byte, error) {
var v interface{}
if err := json.Unmarshal(data, &v); err != nil {
return nil, fmt.Errorf("failed to unmarshal json: %w", err)
}
v = removeHiddenFields(v, hiddenFields)
return []byte(format(v, 1)), nil
}
// FormatToBeautifulJSON dumps v to beautified json bytes.
func FormatToBeautifulJSON(v interface{}, hiddenFields []string) ([]byte, error) {
data, err := json.Marshal(v)
if err != nil {
return nil, fmt.Errorf("failed to marshal json: %w", err)
}
return BeautifyJSONBytes(data, hiddenFields)
}
func format(v interface{}, depth int) string {
switch val := v.(type) {
case string:
return formatString(val)
case float64:
return fmt.Sprint(strconv.FormatFloat(val, 'f', -1, 64))
case bool:
return fmt.Sprint(strconv.FormatBool(val))
case nil:
return "null"
case map[string]interface{}:
return formatMap(val, depth)
case []interface{}:
return formatArray(val, depth)
}
return ""
}
func formatString(s string) string {
r := []rune(s)
if StringMaxLength != 0 && len(r) >= StringMaxLength {
s = string(r[0:StringMaxLength]) + "..."
}
buf := &bytes.Buffer{}
encoder := json.NewEncoder(buf)
encoder.SetEscapeHTML(false)
// TODO: check error
//nolint:errchkjson
_ = encoder.Encode(s)
s = buf.String()
s = strings.TrimSuffix(s, "\n")
return fmt.Sprint(s)
}
func formatMap(m map[string]interface{}, depth int) string {
if len(m) == 0 {
return "{}"
}
currentIndent := generateIndent(depth - 1)
nextIndent := generateIndent(depth)
rows := []string{}
keys := []string{}
for key := range m {
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
val := m[key]
k := fmt.Sprintf(`"%s"`, key)
v := format(val, depth+1)
valueIndent := " "
if Newline == "" {
valueIndent = ""
}
row := fmt.Sprintf("%s%s:%s%s", nextIndent, k, valueIndent, v)
rows = append(rows, row)
}
return fmt.Sprintf("{%s%s%s%s}", Newline, strings.Join(rows, ","+Newline), Newline, currentIndent)
}
func formatArray(a []interface{}, depth int) string {
if len(a) == 0 {
return "[]"
}
currentIndent := generateIndent(depth - 1)
nextIndent := generateIndent(depth)
rows := []string{}
for _, val := range a {
c := format(val, depth+1)
row := nextIndent + c
rows = append(rows, row)
}
return fmt.Sprintf("[%s%s%s%s]", Newline, strings.Join(rows, ","+Newline), Newline, currentIndent)
}
func generateIndent(depth int) string {
return strings.Repeat(" ", Indent*depth)
}
func removeHiddenFields(v interface{}, hiddenFields []string) interface{} {
if _, ok := v.(map[string]interface{}); !ok {
return v
}
// nolint: forcetypeassert
m := v.(map[string]interface{})
// case insensitive key deletion
for _, hiddenField := range hiddenFields {
for k := range m {
if strings.EqualFold(k, hiddenField) {
delete(m, k)
}
}
}
return m
}