VictoriaMetrics/app/vlogscli/json_prettifier.go
Aliaksandr Valialkin daad96b3a5
app/vlogscli: return back sorting result fields by name
This simplifies locating the needed field when the number of fields per each returned result is big
2024-10-07 10:41:48 +02:00

160 lines
3 KiB
Go

package main
import (
"encoding/json"
"fmt"
"io"
"sort"
"sync"
)
type jsonPrettifier struct {
rOriginal io.ReadCloser
d *json.Decoder
pr *io.PipeReader
pw *io.PipeWriter
wg sync.WaitGroup
}
func newJSONPrettifier(r io.ReadCloser) *jsonPrettifier {
d := json.NewDecoder(r)
pr, pw := io.Pipe()
jp := &jsonPrettifier{
rOriginal: r,
d: d,
pr: pr,
pw: pw,
}
jp.wg.Add(1)
go func() {
defer jp.wg.Done()
err := jp.prettifyJSONLines()
jp.closePipesWithError(err)
}()
return jp
}
func (jp *jsonPrettifier) closePipesWithError(err error) {
_ = jp.pr.CloseWithError(err)
_ = jp.pw.CloseWithError(err)
}
func (jp *jsonPrettifier) prettifyJSONLines() error {
for jp.d.More() {
kvs, err := readNextJSONObject(jp.d)
if err != nil {
return err
}
if err := writeJSONObject(jp.pw, kvs); err != nil {
return err
}
}
return nil
}
func (jp *jsonPrettifier) Close() error {
jp.closePipesWithError(io.ErrUnexpectedEOF)
err := jp.rOriginal.Close()
jp.wg.Wait()
return err
}
func (jp *jsonPrettifier) Read(p []byte) (int, error) {
return jp.pr.Read(p)
}
func readNextJSONObject(d *json.Decoder) ([]kv, error) {
t, err := d.Token()
if err != nil {
return nil, fmt.Errorf("cannot read '{': %w", err)
}
delim, ok := t.(json.Delim)
if !ok || delim.String() != "{" {
return nil, fmt.Errorf("unexpected token read; got %q; want '{'", delim)
}
var kvs []kv
for {
// Read object key
t, err := d.Token()
if err != nil {
return nil, fmt.Errorf("cannot read JSON object key or closing brace: %w", err)
}
delim, ok := t.(json.Delim)
if ok {
if delim.String() == "}" {
return kvs, nil
}
return nil, fmt.Errorf("unexpected delimiter read; got %q; want '}'", delim)
}
key, ok := t.(string)
if !ok {
return nil, fmt.Errorf("unexpected token read for object key: %v; want string or '}'", t)
}
// read object value
t, err = d.Token()
if err != nil {
return nil, fmt.Errorf("cannot read JSON object value: %w", err)
}
value, ok := t.(string)
if !ok {
return nil, fmt.Errorf("unexpected token read for oject value: %v; want string", t)
}
kvs = append(kvs, kv{
key: key,
value: value,
})
}
}
func writeJSONObject(w io.Writer, kvs []kv) error {
if len(kvs) == 0 {
fmt.Fprintf(w, "{}\n")
return nil
}
sort.Slice(kvs, func(i, j int) bool {
return kvs[i].key < kvs[j].key
})
fmt.Fprintf(w, "{\n")
if err := writeJSONObjectKeyValue(w, kvs[0]); err != nil {
return err
}
for _, kv := range kvs[1:] {
fmt.Fprintf(w, ",\n")
if err := writeJSONObjectKeyValue(w, kv); err != nil {
return err
}
}
fmt.Fprintf(w, "\n}\n")
return nil
}
func writeJSONObjectKeyValue(w io.Writer, kv kv) error {
key := getJSONString(kv.key)
value := getJSONString(kv.value)
_, err := fmt.Fprintf(w, " %s: %s", key, value)
return err
}
func getJSONString(s string) string {
data, err := json.Marshal(s)
if err != nil {
panic(fmt.Errorf("unexpected error when marshaling string to JSON: %w", err))
}
return string(data)
}
type kv struct {
key string
value string
}