1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
|
// Package jq facilitates processing of JSON strings using jq expressions.
package jq
import (
"bytes"
"encoding/json"
"fmt"
"io"
"math"
"os"
"strconv"
"github.com/cli/go-gh/v2/pkg/jsonpretty"
"github.com/itchyny/gojq"
)
// Evaluate a jq expression against an input and write it to an output.
// Any top-level scalar values produced by the jq expression are written out
// directly, as raw values and not as JSON scalars, similar to how jq --raw
// works.
func Evaluate(input io.Reader, output io.Writer, expr string) error {
return EvaluateFormatted(input, output, expr, "", false)
}
// Evaluate a jq expression against an input and write it to an output,
// optionally with indentation and colorization. Any top-level scalar values
// produced by the jq expression are written out directly, as raw values and not
// as JSON scalars, similar to how jq --raw works.
func EvaluateFormatted(input io.Reader, output io.Writer, expr string, indent string, colorize bool) error {
query, err := gojq.Parse(expr)
if err != nil {
return err
}
code, err := gojq.Compile(
query,
gojq.WithEnvironLoader(func() []string {
return os.Environ()
}))
if err != nil {
return err
}
jsonData, err := io.ReadAll(input)
if err != nil {
return err
}
var responseData interface{}
err = json.Unmarshal(jsonData, &responseData)
if err != nil {
return err
}
enc := prettyEncoder{
w: output,
indent: indent,
colorize: colorize,
}
iter := code.Run(responseData)
for {
v, ok := iter.Next()
if !ok {
break
}
if err, isErr := v.(error); isErr {
return err
}
if text, e := jsonScalarToString(v); e == nil {
_, err := fmt.Fprintln(output, text)
if err != nil {
return err
}
} else {
if err = enc.Encode(v); err != nil {
return err
}
}
}
return nil
}
func jsonScalarToString(input interface{}) (string, error) {
switch tt := input.(type) {
case string:
return tt, nil
case float64:
if math.Trunc(tt) == tt {
return strconv.FormatFloat(tt, 'f', 0, 64), nil
} else {
return strconv.FormatFloat(tt, 'f', 2, 64), nil
}
case nil:
return "", nil
case bool:
return fmt.Sprintf("%v", tt), nil
default:
return "", fmt.Errorf("cannot convert type to string: %v", tt)
}
}
type prettyEncoder struct {
w io.Writer
indent string
colorize bool
}
func (p prettyEncoder) Encode(v any) error {
var b []byte
var err error
if p.indent == "" {
b, err = json.Marshal(v)
} else {
b, err = json.MarshalIndent(v, "", p.indent)
}
if err != nil {
return err
}
if !p.colorize {
if _, err := p.w.Write(b); err != nil {
return err
}
if _, err := p.w.Write([]byte{'\n'}); err != nil {
return err
}
return nil
}
return jsonpretty.Format(p.w, bytes.NewReader(b), p.indent, true)
}
|