2016-08-11 06:07:45 +03:00
|
|
|
// Package gjson provides searching for json strings.
|
|
|
|
package gjson
|
|
|
|
|
2016-08-30 17:21:20 +03:00
|
|
|
import (
|
2017-05-08 04:26:54 +03:00
|
|
|
"encoding/json"
|
2020-02-28 19:37:46 +03:00
|
|
|
"reflect"
|
2016-08-30 17:21:20 +03:00
|
|
|
"strconv"
|
2017-05-08 04:26:54 +03:00
|
|
|
"strings"
|
2017-04-15 03:58:25 +03:00
|
|
|
"time"
|
2017-04-05 20:22:45 +03:00
|
|
|
"unicode/utf16"
|
|
|
|
"unicode/utf8"
|
2020-02-28 19:37:46 +03:00
|
|
|
"unsafe"
|
2016-08-30 17:21:20 +03:00
|
|
|
|
|
|
|
"github.com/tidwall/match"
|
2019-02-17 04:29:39 +03:00
|
|
|
"github.com/tidwall/pretty"
|
2016-08-30 17:21:20 +03:00
|
|
|
)
|
2016-08-11 06:07:45 +03:00
|
|
|
|
|
|
|
// Type is Result type
|
2016-08-18 17:18:24 +03:00
|
|
|
type Type int
|
2016-08-11 06:07:45 +03:00
|
|
|
|
|
|
|
const (
|
|
|
|
// Null is a null json value
|
|
|
|
Null Type = iota
|
|
|
|
// False is a json false boolean
|
|
|
|
False
|
|
|
|
// Number is json number
|
|
|
|
Number
|
|
|
|
// String is a json string
|
|
|
|
String
|
|
|
|
// True is a json true boolean
|
|
|
|
True
|
|
|
|
// JSON is a raw block of JSON
|
|
|
|
JSON
|
|
|
|
)
|
|
|
|
|
2016-10-19 03:13:15 +03:00
|
|
|
// String returns a string representation of the type.
|
|
|
|
func (t Type) String() string {
|
|
|
|
switch t {
|
|
|
|
default:
|
|
|
|
return ""
|
|
|
|
case Null:
|
|
|
|
return "Null"
|
|
|
|
case False:
|
|
|
|
return "False"
|
|
|
|
case Number:
|
|
|
|
return "Number"
|
|
|
|
case String:
|
|
|
|
return "String"
|
|
|
|
case True:
|
|
|
|
return "True"
|
|
|
|
case JSON:
|
|
|
|
return "JSON"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-11 06:07:45 +03:00
|
|
|
// Result represents a json value that is returned from Get().
|
|
|
|
type Result struct {
|
|
|
|
// Type is the json type
|
|
|
|
Type Type
|
|
|
|
// Raw is the raw json
|
|
|
|
Raw string
|
|
|
|
// Str is the json string
|
|
|
|
Str string
|
|
|
|
// Num is the json number
|
|
|
|
Num float64
|
2016-10-18 03:39:27 +03:00
|
|
|
// Index of raw value in original json, zero means index unknown
|
|
|
|
Index int
|
2016-08-11 06:07:45 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// String returns a string representation of the value.
|
|
|
|
func (t Result) String() string {
|
|
|
|
switch t.Type {
|
|
|
|
default:
|
2017-04-18 19:28:51 +03:00
|
|
|
return ""
|
2016-08-11 06:07:45 +03:00
|
|
|
case False:
|
|
|
|
return "false"
|
|
|
|
case Number:
|
2018-07-11 04:10:33 +03:00
|
|
|
if len(t.Raw) == 0 {
|
|
|
|
// calculated result
|
|
|
|
return strconv.FormatFloat(t.Num, 'f', -1, 64)
|
|
|
|
}
|
|
|
|
var i int
|
|
|
|
if t.Raw[0] == '-' {
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
for ; i < len(t.Raw); i++ {
|
|
|
|
if t.Raw[i] < '0' || t.Raw[i] > '9' {
|
|
|
|
return strconv.FormatFloat(t.Num, 'f', -1, 64)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return t.Raw
|
2016-08-11 06:07:45 +03:00
|
|
|
case String:
|
|
|
|
return t.Str
|
|
|
|
case JSON:
|
|
|
|
return t.Raw
|
|
|
|
case True:
|
|
|
|
return "true"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-22 16:05:51 +03:00
|
|
|
// Bool returns an boolean representation.
|
|
|
|
func (t Result) Bool() bool {
|
|
|
|
switch t.Type {
|
|
|
|
default:
|
|
|
|
return false
|
|
|
|
case True:
|
|
|
|
return true
|
|
|
|
case String:
|
update Result.Bool to use golang bool parsing
golang has several tools that already parse boolean values in the standard library.
Specifically strconv.ParseBool(string)
https://golang.org/pkg/strconv/#ParseBool
"ParseBool returns the boolean value represented by the string. It accepts 1, t, T, TRUE, true, True, 0, f, F, FALSE, false, False. Any other value returns an error."
This change returns any matching ParseBool string to that boolean value, or if the string is not one of those values, the error being thrown, returns a false.
2020-05-30 13:16:06 +03:00
|
|
|
b, err := strconv.ParseBool(t.Str)
|
|
|
|
return !(!b || err != nil)
|
2016-08-22 16:05:51 +03:00
|
|
|
case Number:
|
|
|
|
return t.Num != 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Int returns an integer representation.
|
|
|
|
func (t Result) Int() int64 {
|
|
|
|
switch t.Type {
|
|
|
|
default:
|
|
|
|
return 0
|
|
|
|
case True:
|
|
|
|
return 1
|
|
|
|
case String:
|
2017-05-26 05:39:18 +03:00
|
|
|
n, _ := parseInt(t.Str)
|
2016-08-22 16:05:51 +03:00
|
|
|
return n
|
|
|
|
case Number:
|
2017-05-26 05:39:18 +03:00
|
|
|
// try to directly convert the float64 to int64
|
|
|
|
n, ok := floatToInt(t.Num)
|
|
|
|
if !ok {
|
|
|
|
// now try to parse the raw string
|
|
|
|
n, ok = parseInt(t.Raw)
|
|
|
|
if !ok {
|
|
|
|
// fallback to a standard conversion
|
|
|
|
return int64(t.Num)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return n
|
2016-08-22 16:05:51 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-02 18:24:13 +03:00
|
|
|
// Uint returns an unsigned integer representation.
|
|
|
|
func (t Result) Uint() uint64 {
|
|
|
|
switch t.Type {
|
|
|
|
default:
|
|
|
|
return 0
|
|
|
|
case True:
|
|
|
|
return 1
|
|
|
|
case String:
|
2017-05-26 05:39:18 +03:00
|
|
|
n, _ := parseUint(t.Str)
|
2016-11-02 18:24:13 +03:00
|
|
|
return n
|
|
|
|
case Number:
|
2017-05-26 05:39:18 +03:00
|
|
|
// try to directly convert the float64 to uint64
|
|
|
|
n, ok := floatToUint(t.Num)
|
|
|
|
if !ok {
|
|
|
|
// now try to parse the raw string
|
|
|
|
n, ok = parseUint(t.Raw)
|
|
|
|
if !ok {
|
|
|
|
// fallback to a standard conversion
|
|
|
|
return uint64(t.Num)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return n
|
2016-11-02 18:24:13 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-22 16:05:51 +03:00
|
|
|
// Float returns an float64 representation.
|
|
|
|
func (t Result) Float() float64 {
|
|
|
|
switch t.Type {
|
|
|
|
default:
|
|
|
|
return 0
|
|
|
|
case True:
|
|
|
|
return 1
|
|
|
|
case String:
|
|
|
|
n, _ := strconv.ParseFloat(t.Str, 64)
|
|
|
|
return n
|
|
|
|
case Number:
|
|
|
|
return t.Num
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-15 03:58:25 +03:00
|
|
|
// Time returns a time.Time representation.
|
|
|
|
func (t Result) Time() time.Time {
|
|
|
|
res, _ := time.Parse(time.RFC3339, t.String())
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
2016-11-02 23:45:25 +03:00
|
|
|
// Array returns back an array of values.
|
2019-06-28 04:03:36 +03:00
|
|
|
// If the result represents a non-existent value, then an empty array will be
|
|
|
|
// returned. If the result is not a JSON array, the return value will be an
|
|
|
|
// array containing one result.
|
2016-08-22 16:05:51 +03:00
|
|
|
func (t Result) Array() []Result {
|
2017-12-02 00:13:24 +03:00
|
|
|
if t.Type == Null {
|
|
|
|
return []Result{}
|
2016-11-02 23:45:25 +03:00
|
|
|
}
|
2016-08-22 16:05:51 +03:00
|
|
|
if t.Type != JSON {
|
2016-11-02 22:20:24 +03:00
|
|
|
return []Result{t}
|
2016-08-22 16:05:51 +03:00
|
|
|
}
|
2016-08-26 06:17:32 +03:00
|
|
|
r := t.arrayOrMap('[', false)
|
|
|
|
return r.a
|
2016-08-22 16:05:51 +03:00
|
|
|
}
|
|
|
|
|
2017-08-14 18:23:21 +03:00
|
|
|
// IsObject returns true if the result value is a JSON object.
|
|
|
|
func (t Result) IsObject() bool {
|
|
|
|
return t.Type == JSON && len(t.Raw) > 0 && t.Raw[0] == '{'
|
|
|
|
}
|
|
|
|
|
2017-12-02 00:13:24 +03:00
|
|
|
// IsArray returns true if the result value is a JSON array.
|
2017-08-14 18:23:21 +03:00
|
|
|
func (t Result) IsArray() bool {
|
|
|
|
return t.Type == JSON && len(t.Raw) > 0 && t.Raw[0] == '['
|
|
|
|
}
|
|
|
|
|
2016-11-30 20:50:59 +03:00
|
|
|
// ForEach iterates through values.
|
2019-06-28 04:03:36 +03:00
|
|
|
// If the result represents a non-existent value, then no values will be
|
|
|
|
// iterated. If the result is an Object, the iterator will pass the key and
|
|
|
|
// value of each item. If the result is an Array, the iterator will only pass
|
|
|
|
// the value of each item. If the result is not a JSON array or object, the
|
|
|
|
// iterator will pass back one value equal to the result.
|
2016-11-30 20:50:59 +03:00
|
|
|
func (t Result) ForEach(iterator func(key, value Result) bool) {
|
|
|
|
if !t.Exists() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if t.Type != JSON {
|
|
|
|
iterator(Result{}, t)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
json := t.Raw
|
|
|
|
var keys bool
|
|
|
|
var i int
|
|
|
|
var key, value Result
|
|
|
|
for ; i < len(json); i++ {
|
|
|
|
if json[i] == '{' {
|
|
|
|
i++
|
|
|
|
key.Type = String
|
|
|
|
keys = true
|
|
|
|
break
|
|
|
|
} else if json[i] == '[' {
|
|
|
|
i++
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if json[i] > ' ' {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
var str string
|
|
|
|
var vesc bool
|
|
|
|
var ok bool
|
|
|
|
for ; i < len(json); i++ {
|
|
|
|
if keys {
|
|
|
|
if json[i] != '"' {
|
|
|
|
continue
|
|
|
|
}
|
2016-12-08 02:37:33 +03:00
|
|
|
s := i
|
2016-11-30 20:50:59 +03:00
|
|
|
i, str, vesc, ok = parseString(json, i+1)
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if vesc {
|
|
|
|
key.Str = unescape(str[1 : len(str)-1])
|
|
|
|
} else {
|
|
|
|
key.Str = str[1 : len(str)-1]
|
|
|
|
}
|
|
|
|
key.Raw = str
|
2016-12-08 02:37:33 +03:00
|
|
|
key.Index = s
|
2016-11-30 20:50:59 +03:00
|
|
|
}
|
2016-12-09 01:00:05 +03:00
|
|
|
for ; i < len(json); i++ {
|
|
|
|
if json[i] <= ' ' || json[i] == ',' || json[i] == ':' {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
s := i
|
2016-11-30 20:50:59 +03:00
|
|
|
i, value, ok = parseAny(json, i, true)
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
2016-12-09 01:00:05 +03:00
|
|
|
value.Index = s
|
2016-11-30 20:50:59 +03:00
|
|
|
if !iterator(key, value) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-02 23:45:25 +03:00
|
|
|
// Map returns back an map of values. The result should be a JSON array.
|
2016-08-22 16:05:51 +03:00
|
|
|
func (t Result) Map() map[string]Result {
|
|
|
|
if t.Type != JSON {
|
|
|
|
return map[string]Result{}
|
|
|
|
}
|
2016-08-26 06:17:32 +03:00
|
|
|
r := t.arrayOrMap('{', false)
|
|
|
|
return r.o
|
2016-08-22 16:05:51 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get searches result for the specified path.
|
|
|
|
// The result should be a JSON array or object.
|
|
|
|
func (t Result) Get(path string) Result {
|
|
|
|
return Get(t.Raw, path)
|
|
|
|
}
|
|
|
|
|
2016-08-26 06:17:32 +03:00
|
|
|
type arrayOrMapResult struct {
|
|
|
|
a []Result
|
|
|
|
ai []interface{}
|
|
|
|
o map[string]Result
|
|
|
|
oi map[string]interface{}
|
|
|
|
vc byte
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t Result) arrayOrMap(vc byte, valueize bool) (r arrayOrMapResult) {
|
2016-08-22 16:05:51 +03:00
|
|
|
var json = t.Raw
|
|
|
|
var i int
|
|
|
|
var value Result
|
|
|
|
var count int
|
|
|
|
var key Result
|
|
|
|
if vc == 0 {
|
|
|
|
for ; i < len(json); i++ {
|
|
|
|
if json[i] == '{' || json[i] == '[' {
|
2016-08-26 06:17:32 +03:00
|
|
|
r.vc = json[i]
|
2016-08-22 16:05:51 +03:00
|
|
|
i++
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if json[i] > ' ' {
|
|
|
|
goto end
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for ; i < len(json); i++ {
|
|
|
|
if json[i] == vc {
|
|
|
|
i++
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if json[i] > ' ' {
|
|
|
|
goto end
|
|
|
|
}
|
|
|
|
}
|
2016-08-26 06:17:32 +03:00
|
|
|
r.vc = vc
|
2016-08-22 16:05:51 +03:00
|
|
|
}
|
2016-08-26 06:17:32 +03:00
|
|
|
if r.vc == '{' {
|
2016-08-24 22:12:07 +03:00
|
|
|
if valueize {
|
2016-08-26 06:17:32 +03:00
|
|
|
r.oi = make(map[string]interface{})
|
2016-08-24 22:12:07 +03:00
|
|
|
} else {
|
2016-08-26 06:17:32 +03:00
|
|
|
r.o = make(map[string]Result)
|
2016-08-24 22:12:07 +03:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if valueize {
|
2016-08-26 06:17:32 +03:00
|
|
|
r.ai = make([]interface{}, 0)
|
2016-08-24 22:12:07 +03:00
|
|
|
} else {
|
2016-08-26 06:17:32 +03:00
|
|
|
r.a = make([]Result, 0)
|
2016-08-24 22:12:07 +03:00
|
|
|
}
|
|
|
|
}
|
2016-08-22 16:05:51 +03:00
|
|
|
for ; i < len(json); i++ {
|
|
|
|
if json[i] <= ' ' {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
// get next value
|
|
|
|
if json[i] == ']' || json[i] == '}' {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
switch json[i] {
|
|
|
|
default:
|
|
|
|
if (json[i] >= '0' && json[i] <= '9') || json[i] == '-' {
|
|
|
|
value.Type = Number
|
|
|
|
value.Raw, value.Num = tonum(json[i:])
|
2018-08-02 18:58:17 +03:00
|
|
|
value.Str = ""
|
2016-08-22 16:05:51 +03:00
|
|
|
} else {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
case '{', '[':
|
|
|
|
value.Type = JSON
|
|
|
|
value.Raw = squash(json[i:])
|
2018-08-02 18:58:17 +03:00
|
|
|
value.Str, value.Num = "", 0
|
2016-08-22 16:05:51 +03:00
|
|
|
case 'n':
|
|
|
|
value.Type = Null
|
|
|
|
value.Raw = tolit(json[i:])
|
2018-08-02 18:58:17 +03:00
|
|
|
value.Str, value.Num = "", 0
|
2016-08-22 16:05:51 +03:00
|
|
|
case 't':
|
|
|
|
value.Type = True
|
|
|
|
value.Raw = tolit(json[i:])
|
2018-08-02 18:58:17 +03:00
|
|
|
value.Str, value.Num = "", 0
|
2016-08-22 16:05:51 +03:00
|
|
|
case 'f':
|
|
|
|
value.Type = False
|
|
|
|
value.Raw = tolit(json[i:])
|
2018-08-02 18:58:17 +03:00
|
|
|
value.Str, value.Num = "", 0
|
2016-08-22 16:05:51 +03:00
|
|
|
case '"':
|
|
|
|
value.Type = String
|
|
|
|
value.Raw, value.Str = tostr(json[i:])
|
2018-08-02 18:58:17 +03:00
|
|
|
value.Num = 0
|
2016-08-22 16:05:51 +03:00
|
|
|
}
|
|
|
|
i += len(value.Raw) - 1
|
|
|
|
|
2016-08-26 06:17:32 +03:00
|
|
|
if r.vc == '{' {
|
2016-08-22 16:05:51 +03:00
|
|
|
if count%2 == 0 {
|
|
|
|
key = value
|
|
|
|
} else {
|
2016-08-24 22:12:07 +03:00
|
|
|
if valueize {
|
2018-07-31 00:44:31 +03:00
|
|
|
if _, ok := r.oi[key.Str]; !ok {
|
|
|
|
r.oi[key.Str] = value.Value()
|
|
|
|
}
|
2016-08-24 22:12:07 +03:00
|
|
|
} else {
|
2018-07-31 00:44:31 +03:00
|
|
|
if _, ok := r.o[key.Str]; !ok {
|
|
|
|
r.o[key.Str] = value
|
|
|
|
}
|
2016-08-24 22:12:07 +03:00
|
|
|
}
|
2016-08-22 16:05:51 +03:00
|
|
|
}
|
|
|
|
count++
|
|
|
|
} else {
|
2016-08-24 22:12:07 +03:00
|
|
|
if valueize {
|
2016-08-26 06:17:32 +03:00
|
|
|
r.ai = append(r.ai, value.Value())
|
2016-08-24 22:12:07 +03:00
|
|
|
} else {
|
2016-08-26 06:17:32 +03:00
|
|
|
r.a = append(r.a, value)
|
2016-08-24 22:12:07 +03:00
|
|
|
}
|
2016-08-22 16:05:51 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
end:
|
2016-08-26 06:17:32 +03:00
|
|
|
return
|
2016-08-22 16:05:51 +03:00
|
|
|
}
|
|
|
|
|
2016-12-02 21:59:39 +03:00
|
|
|
// Parse parses the json and returns a result.
|
2018-02-18 20:05:23 +03:00
|
|
|
//
|
|
|
|
// This function expects that the json is well-formed, and does not validate.
|
|
|
|
// Invalid json will not panic, but it may return back unexpected results.
|
|
|
|
// If you are consuming JSON from an unpredictable source then you may want to
|
|
|
|
// use the Valid function first.
|
2016-08-22 16:05:51 +03:00
|
|
|
func Parse(json string) Result {
|
|
|
|
var value Result
|
|
|
|
for i := 0; i < len(json); i++ {
|
2016-08-24 22:12:07 +03:00
|
|
|
if json[i] == '{' || json[i] == '[' {
|
|
|
|
value.Type = JSON
|
|
|
|
value.Raw = json[i:] // just take the entire raw
|
|
|
|
break
|
|
|
|
}
|
2016-08-22 16:05:51 +03:00
|
|
|
if json[i] <= ' ' {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
switch json[i] {
|
|
|
|
default:
|
|
|
|
if (json[i] >= '0' && json[i] <= '9') || json[i] == '-' {
|
|
|
|
value.Type = Number
|
|
|
|
value.Raw, value.Num = tonum(json[i:])
|
|
|
|
} else {
|
|
|
|
return Result{}
|
|
|
|
}
|
|
|
|
case 'n':
|
|
|
|
value.Type = Null
|
|
|
|
value.Raw = tolit(json[i:])
|
|
|
|
case 't':
|
|
|
|
value.Type = True
|
|
|
|
value.Raw = tolit(json[i:])
|
|
|
|
case 'f':
|
|
|
|
value.Type = False
|
|
|
|
value.Raw = tolit(json[i:])
|
|
|
|
case '"':
|
|
|
|
value.Type = String
|
|
|
|
value.Raw, value.Str = tostr(json[i:])
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
return value
|
|
|
|
}
|
|
|
|
|
2016-12-02 21:59:39 +03:00
|
|
|
// ParseBytes parses the json and returns a result.
|
|
|
|
// If working with bytes, this method preferred over Parse(string(data))
|
|
|
|
func ParseBytes(json []byte) Result {
|
|
|
|
return Parse(string(json))
|
|
|
|
}
|
|
|
|
|
2016-08-22 16:05:51 +03:00
|
|
|
func squash(json string) string {
|
2019-11-18 19:51:37 +03:00
|
|
|
// expects that the lead character is a '[' or '{' or '(' or '"'
|
2016-08-22 16:05:51 +03:00
|
|
|
// squash the value, ignoring all nested arrays and objects.
|
2019-11-18 19:51:37 +03:00
|
|
|
var i, depth int
|
|
|
|
if json[0] != '"' {
|
|
|
|
i, depth = 1, 1
|
|
|
|
}
|
|
|
|
for ; i < len(json); i++ {
|
2016-08-22 16:05:51 +03:00
|
|
|
if json[i] >= '"' && json[i] <= '}' {
|
|
|
|
switch json[i] {
|
|
|
|
case '"':
|
|
|
|
i++
|
|
|
|
s2 := i
|
|
|
|
for ; i < len(json); i++ {
|
|
|
|
if json[i] > '\\' {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if json[i] == '"' {
|
|
|
|
// look for an escaped slash
|
|
|
|
if json[i-1] == '\\' {
|
|
|
|
n := 0
|
|
|
|
for j := i - 2; j > s2-1; j-- {
|
|
|
|
if json[j] != '\\' {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
n++
|
|
|
|
}
|
|
|
|
if n%2 == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2019-11-18 19:51:37 +03:00
|
|
|
if depth == 0 {
|
|
|
|
return json[:i+1]
|
|
|
|
}
|
2019-11-01 15:08:48 +03:00
|
|
|
case '{', '[', '(':
|
2016-08-22 16:05:51 +03:00
|
|
|
depth++
|
2019-11-01 15:08:48 +03:00
|
|
|
case '}', ']', ')':
|
2016-08-22 16:05:51 +03:00
|
|
|
depth--
|
|
|
|
if depth == 0 {
|
|
|
|
return json[:i+1]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return json
|
|
|
|
}
|
|
|
|
|
|
|
|
func tonum(json string) (raw string, num float64) {
|
|
|
|
for i := 1; i < len(json); i++ {
|
|
|
|
// less than dash might have valid characters
|
|
|
|
if json[i] <= '-' {
|
|
|
|
if json[i] <= ' ' || json[i] == ',' {
|
|
|
|
// break on whitespace and comma
|
|
|
|
raw = json[:i]
|
|
|
|
num, _ = strconv.ParseFloat(raw, 64)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// could be a '+' or '-'. let's assume so.
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if json[i] < ']' {
|
|
|
|
// probably a valid number
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if json[i] == 'e' || json[i] == 'E' {
|
|
|
|
// allow for exponential numbers
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
// likely a ']' or '}'
|
|
|
|
raw = json[:i]
|
|
|
|
num, _ = strconv.ParseFloat(raw, 64)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
raw = json
|
|
|
|
num, _ = strconv.ParseFloat(raw, 64)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func tolit(json string) (raw string) {
|
|
|
|
for i := 1; i < len(json); i++ {
|
2017-11-20 20:55:52 +03:00
|
|
|
if json[i] < 'a' || json[i] > 'z' {
|
2016-08-22 16:05:51 +03:00
|
|
|
return json[:i]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return json
|
|
|
|
}
|
|
|
|
|
|
|
|
func tostr(json string) (raw string, str string) {
|
|
|
|
// expects that the lead character is a '"'
|
|
|
|
for i := 1; i < len(json); i++ {
|
|
|
|
if json[i] > '\\' {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if json[i] == '"' {
|
|
|
|
return json[:i+1], json[1:i]
|
|
|
|
}
|
|
|
|
if json[i] == '\\' {
|
|
|
|
i++
|
|
|
|
for ; i < len(json); i++ {
|
|
|
|
if json[i] > '\\' {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if json[i] == '"' {
|
|
|
|
// look for an escaped slash
|
|
|
|
if json[i-1] == '\\' {
|
|
|
|
n := 0
|
|
|
|
for j := i - 2; j > 0; j-- {
|
|
|
|
if json[j] != '\\' {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
n++
|
|
|
|
}
|
|
|
|
if n%2 == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2016-10-19 03:13:15 +03:00
|
|
|
var ret string
|
|
|
|
if i+1 < len(json) {
|
|
|
|
ret = json[:i+1]
|
|
|
|
} else {
|
|
|
|
ret = json[:i]
|
|
|
|
}
|
|
|
|
return ret, unescape(json[1:i])
|
2016-08-22 16:05:51 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return json, json[1:]
|
|
|
|
}
|
|
|
|
|
2016-08-12 18:39:08 +03:00
|
|
|
// Exists returns true if value exists.
|
|
|
|
//
|
|
|
|
// if gjson.Get(json, "name.last").Exists(){
|
|
|
|
// println("value exists")
|
|
|
|
// }
|
|
|
|
func (t Result) Exists() bool {
|
2016-08-12 18:51:56 +03:00
|
|
|
return t.Type != Null || len(t.Raw) != 0
|
2016-08-12 18:39:08 +03:00
|
|
|
}
|
|
|
|
|
2016-08-11 06:07:45 +03:00
|
|
|
// Value returns one of these types:
|
|
|
|
//
|
|
|
|
// bool, for JSON booleans
|
|
|
|
// float64, for JSON numbers
|
|
|
|
// Number, for JSON numbers
|
|
|
|
// string, for JSON string literals
|
|
|
|
// nil, for JSON null
|
2018-02-28 06:21:22 +03:00
|
|
|
// map[string]interface{}, for JSON objects
|
|
|
|
// []interface{}, for JSON arrays
|
2016-08-11 06:07:45 +03:00
|
|
|
//
|
|
|
|
func (t Result) Value() interface{} {
|
2016-08-21 20:10:55 +03:00
|
|
|
if t.Type == String {
|
|
|
|
return t.Str
|
|
|
|
}
|
2016-08-11 06:07:45 +03:00
|
|
|
switch t.Type {
|
|
|
|
default:
|
|
|
|
return nil
|
|
|
|
case False:
|
|
|
|
return false
|
|
|
|
case Number:
|
|
|
|
return t.Num
|
|
|
|
case JSON:
|
2016-08-26 06:17:32 +03:00
|
|
|
r := t.arrayOrMap(0, true)
|
|
|
|
if r.vc == '{' {
|
|
|
|
return r.oi
|
|
|
|
} else if r.vc == '[' {
|
|
|
|
return r.ai
|
2016-08-22 16:05:51 +03:00
|
|
|
}
|
|
|
|
return nil
|
2016-08-11 06:07:45 +03:00
|
|
|
case True:
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-25 19:40:50 +03:00
|
|
|
func parseString(json string, i int) (int, string, bool, bool) {
|
2016-08-25 17:21:53 +03:00
|
|
|
var s = i
|
|
|
|
for ; i < len(json); i++ {
|
|
|
|
if json[i] > '\\' {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if json[i] == '"' {
|
2016-08-25 19:40:50 +03:00
|
|
|
return i + 1, json[s-1 : i+1], false, true
|
2016-08-25 17:21:53 +03:00
|
|
|
}
|
|
|
|
if json[i] == '\\' {
|
|
|
|
i++
|
|
|
|
for ; i < len(json); i++ {
|
|
|
|
if json[i] > '\\' {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if json[i] == '"' {
|
|
|
|
// look for an escaped slash
|
|
|
|
if json[i-1] == '\\' {
|
|
|
|
n := 0
|
|
|
|
for j := i - 2; j > 0; j-- {
|
|
|
|
if json[j] != '\\' {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
n++
|
|
|
|
}
|
|
|
|
if n%2 == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
2016-08-25 19:40:50 +03:00
|
|
|
return i + 1, json[s-1 : i+1], true, true
|
2016-08-25 17:21:53 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2016-08-25 19:40:50 +03:00
|
|
|
return i, json[s-1:], false, false
|
2016-08-11 20:39:38 +03:00
|
|
|
}
|
|
|
|
|
2016-08-25 17:21:53 +03:00
|
|
|
func parseNumber(json string, i int) (int, string) {
|
|
|
|
var s = i
|
|
|
|
i++
|
|
|
|
for ; i < len(json); i++ {
|
2019-06-28 04:03:36 +03:00
|
|
|
if json[i] <= ' ' || json[i] == ',' || json[i] == ']' ||
|
|
|
|
json[i] == '}' {
|
2016-08-25 17:21:53 +03:00
|
|
|
return i, json[s:i]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return i, json[s:]
|
2016-08-11 20:39:38 +03:00
|
|
|
}
|
|
|
|
|
2016-08-25 17:21:53 +03:00
|
|
|
func parseLiteral(json string, i int) (int, string) {
|
|
|
|
var s = i
|
|
|
|
i++
|
|
|
|
for ; i < len(json); i++ {
|
|
|
|
if json[i] < 'a' || json[i] > 'z' {
|
|
|
|
return i, json[s:i]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return i, json[s:]
|
|
|
|
}
|
2016-08-11 06:07:45 +03:00
|
|
|
|
2016-08-25 20:05:03 +03:00
|
|
|
type arrayPathResult struct {
|
|
|
|
part string
|
|
|
|
path string
|
2019-02-17 04:29:39 +03:00
|
|
|
pipe string
|
|
|
|
piped bool
|
2016-08-25 20:05:03 +03:00
|
|
|
more bool
|
|
|
|
alogok bool
|
|
|
|
arrch bool
|
|
|
|
alogkey string
|
2016-08-31 23:23:20 +03:00
|
|
|
query struct {
|
|
|
|
on bool
|
|
|
|
path string
|
|
|
|
op string
|
|
|
|
value string
|
2016-11-30 17:52:25 +03:00
|
|
|
all bool
|
2016-08-31 23:23:20 +03:00
|
|
|
}
|
2016-08-25 20:05:03 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func parseArrayPath(path string) (r arrayPathResult) {
|
2016-08-11 20:39:38 +03:00
|
|
|
for i := 0; i < len(path); i++ {
|
2019-06-29 22:10:41 +03:00
|
|
|
if path[i] == '|' {
|
|
|
|
r.part = path[:i]
|
|
|
|
r.pipe = path[i+1:]
|
|
|
|
r.piped = true
|
|
|
|
return
|
2019-02-17 04:29:39 +03:00
|
|
|
}
|
2016-08-22 17:39:41 +03:00
|
|
|
if path[i] == '.' {
|
2016-08-25 20:05:03 +03:00
|
|
|
r.part = path[:i]
|
|
|
|
r.path = path[i+1:]
|
|
|
|
r.more = true
|
|
|
|
return
|
2016-08-25 17:21:53 +03:00
|
|
|
}
|
|
|
|
if path[i] == '#' {
|
2016-08-25 20:05:03 +03:00
|
|
|
r.arrch = true
|
2016-08-31 23:23:20 +03:00
|
|
|
if i == 0 && len(path) > 1 {
|
|
|
|
if path[1] == '.' {
|
|
|
|
r.alogok = true
|
|
|
|
r.alogkey = path[2:]
|
|
|
|
r.path = path[:1]
|
2019-06-29 21:31:27 +03:00
|
|
|
} else if path[1] == '[' || path[1] == '(' {
|
2016-08-31 23:23:20 +03:00
|
|
|
// query
|
2019-07-12 16:14:45 +03:00
|
|
|
r.query.on = true
|
|
|
|
if true {
|
|
|
|
qpath, op, value, _, fi, ok := parseQuery(path[i:])
|
|
|
|
if !ok {
|
|
|
|
// bad query, end now
|
2016-08-31 23:23:20 +03:00
|
|
|
break
|
|
|
|
}
|
2019-07-12 16:14:45 +03:00
|
|
|
r.query.path = qpath
|
|
|
|
r.query.op = op
|
|
|
|
r.query.value = value
|
|
|
|
i = fi - 1
|
|
|
|
if i+1 < len(path) && path[i+1] == '#' {
|
|
|
|
r.query.all = true
|
2016-08-31 23:23:20 +03:00
|
|
|
}
|
2019-07-12 16:14:45 +03:00
|
|
|
} else {
|
|
|
|
var end byte
|
|
|
|
if path[1] == '[' {
|
|
|
|
end = ']'
|
|
|
|
} else {
|
|
|
|
end = ')'
|
2016-08-31 23:23:20 +03:00
|
|
|
}
|
2019-07-12 16:14:45 +03:00
|
|
|
i += 2
|
|
|
|
// whitespace
|
|
|
|
for ; i < len(path); i++ {
|
|
|
|
if path[i] > ' ' {
|
|
|
|
break
|
2016-08-31 23:23:20 +03:00
|
|
|
}
|
2019-07-12 16:14:45 +03:00
|
|
|
}
|
|
|
|
s := i
|
|
|
|
for ; i < len(path); i++ {
|
|
|
|
if path[i] <= ' ' ||
|
|
|
|
path[i] == '!' ||
|
|
|
|
path[i] == '=' ||
|
|
|
|
path[i] == '<' ||
|
|
|
|
path[i] == '>' ||
|
|
|
|
path[i] == '%' ||
|
|
|
|
path[i] == end {
|
|
|
|
break
|
2016-08-31 23:23:20 +03:00
|
|
|
}
|
|
|
|
}
|
2019-07-12 16:14:45 +03:00
|
|
|
r.query.path = path[s:i]
|
2016-08-31 23:23:20 +03:00
|
|
|
// whitespace
|
|
|
|
for ; i < len(path); i++ {
|
|
|
|
if path[i] > ' ' {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2019-07-12 16:14:45 +03:00
|
|
|
if i < len(path) {
|
|
|
|
s = i
|
|
|
|
if path[i] == '!' {
|
|
|
|
if i < len(path)-1 && (path[i+1] == '=' ||
|
|
|
|
path[i+1] == '%') {
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
} else if path[i] == '<' || path[i] == '>' {
|
|
|
|
if i < len(path)-1 && path[i+1] == '=' {
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
} else if path[i] == '=' {
|
|
|
|
if i < len(path)-1 && path[i+1] == '=' {
|
|
|
|
s++
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
i++
|
|
|
|
r.query.op = path[s:i]
|
|
|
|
// whitespace
|
|
|
|
for ; i < len(path); i++ {
|
|
|
|
if path[i] > ' ' {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
s = i
|
|
|
|
for ; i < len(path); i++ {
|
|
|
|
if path[i] == '"' {
|
|
|
|
i++
|
|
|
|
s2 := i
|
|
|
|
for ; i < len(path); i++ {
|
|
|
|
if path[i] > '\\' {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if path[i] == '"' {
|
|
|
|
// look for an escaped slash
|
|
|
|
if path[i-1] == '\\' {
|
|
|
|
n := 0
|
|
|
|
for j := i - 2; j > s2-1; j-- {
|
|
|
|
if path[j] != '\\' {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
n++
|
|
|
|
}
|
|
|
|
if n%2 == 0 {
|
|
|
|
continue
|
2016-08-31 23:23:20 +03:00
|
|
|
}
|
|
|
|
}
|
2019-07-12 16:14:45 +03:00
|
|
|
break
|
2016-08-31 23:23:20 +03:00
|
|
|
}
|
|
|
|
}
|
2019-07-12 16:14:45 +03:00
|
|
|
} else if path[i] == end {
|
|
|
|
if i+1 < len(path) && path[i+1] == '#' {
|
|
|
|
r.query.all = true
|
|
|
|
}
|
|
|
|
break
|
2016-08-31 23:23:20 +03:00
|
|
|
}
|
|
|
|
}
|
2019-07-12 16:14:45 +03:00
|
|
|
if i > len(path) {
|
|
|
|
i = len(path)
|
|
|
|
}
|
|
|
|
v := path[s:i]
|
|
|
|
for len(v) > 0 && v[len(v)-1] <= ' ' {
|
|
|
|
v = v[:len(v)-1]
|
|
|
|
}
|
|
|
|
r.query.value = v
|
2016-08-31 23:23:20 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-08-21 15:35:33 +03:00
|
|
|
}
|
2016-08-22 17:39:41 +03:00
|
|
|
continue
|
|
|
|
}
|
2016-08-25 17:21:53 +03:00
|
|
|
}
|
2016-08-25 20:05:03 +03:00
|
|
|
r.part = path
|
|
|
|
r.path = ""
|
|
|
|
return
|
2016-08-25 17:21:53 +03:00
|
|
|
}
|
|
|
|
|
2019-07-12 16:14:45 +03:00
|
|
|
// splitQuery takes a query and splits it into three parts:
|
|
|
|
// path, op, middle, and right.
|
|
|
|
// So for this query:
|
|
|
|
// #(first_name=="Murphy").last
|
|
|
|
// Becomes
|
|
|
|
// first_name # path
|
|
|
|
// =="Murphy" # middle
|
|
|
|
// .last # right
|
|
|
|
// Or,
|
|
|
|
// #(service_roles.#(=="one")).cap
|
|
|
|
// Becomes
|
|
|
|
// service_roles.#(=="one") # path
|
|
|
|
// # middle
|
|
|
|
// .cap # right
|
|
|
|
func parseQuery(query string) (
|
|
|
|
path, op, value, remain string, i int, ok bool,
|
|
|
|
) {
|
|
|
|
if len(query) < 2 || query[0] != '#' ||
|
|
|
|
(query[1] != '(' && query[1] != '[') {
|
|
|
|
return "", "", "", "", i, false
|
|
|
|
}
|
|
|
|
i = 2
|
|
|
|
j := 0 // start of value part
|
|
|
|
depth := 1
|
|
|
|
for ; i < len(query); i++ {
|
|
|
|
if depth == 1 && j == 0 {
|
|
|
|
switch query[i] {
|
|
|
|
case '!', '=', '<', '>', '%':
|
|
|
|
// start of the value part
|
|
|
|
j = i
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if query[i] == '\\' {
|
|
|
|
i++
|
|
|
|
} else if query[i] == '[' || query[i] == '(' {
|
|
|
|
depth++
|
|
|
|
} else if query[i] == ']' || query[i] == ')' {
|
|
|
|
depth--
|
|
|
|
if depth == 0 {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
} else if query[i] == '"' {
|
|
|
|
// inside selector string, balance quotes
|
|
|
|
i++
|
|
|
|
for ; i < len(query); i++ {
|
|
|
|
if query[i] == '\\' {
|
|
|
|
i++
|
|
|
|
} else if query[i] == '"' {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if depth > 0 {
|
|
|
|
return "", "", "", "", i, false
|
|
|
|
}
|
|
|
|
if j > 0 {
|
|
|
|
path = trim(query[2:j])
|
|
|
|
value = trim(query[j:i])
|
|
|
|
remain = query[i+1:]
|
|
|
|
// parse the compare op from the value
|
|
|
|
var opsz int
|
|
|
|
switch {
|
|
|
|
case len(value) == 1:
|
|
|
|
opsz = 1
|
|
|
|
case value[0] == '!' && value[1] == '=':
|
|
|
|
opsz = 2
|
|
|
|
case value[0] == '!' && value[1] == '%':
|
|
|
|
opsz = 2
|
|
|
|
case value[0] == '<' && value[1] == '=':
|
|
|
|
opsz = 2
|
|
|
|
case value[0] == '>' && value[1] == '=':
|
|
|
|
opsz = 2
|
|
|
|
case value[0] == '=' && value[1] == '=':
|
|
|
|
value = value[1:]
|
|
|
|
opsz = 1
|
|
|
|
case value[0] == '<':
|
|
|
|
opsz = 1
|
|
|
|
case value[0] == '>':
|
|
|
|
opsz = 1
|
|
|
|
case value[0] == '=':
|
|
|
|
opsz = 1
|
|
|
|
case value[0] == '%':
|
|
|
|
opsz = 1
|
|
|
|
}
|
|
|
|
op = value[:opsz]
|
|
|
|
value = trim(value[opsz:])
|
|
|
|
} else {
|
|
|
|
path = trim(query[2:i])
|
|
|
|
remain = query[i+1:]
|
|
|
|
}
|
|
|
|
return path, op, value, remain, i + 1, true
|
|
|
|
}
|
|
|
|
|
|
|
|
func trim(s string) string {
|
|
|
|
left:
|
|
|
|
if len(s) > 0 && s[0] <= ' ' {
|
|
|
|
s = s[1:]
|
|
|
|
goto left
|
|
|
|
}
|
|
|
|
right:
|
|
|
|
if len(s) > 0 && s[len(s)-1] <= ' ' {
|
|
|
|
s = s[:len(s)-1]
|
|
|
|
goto right
|
|
|
|
}
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
2016-08-25 20:05:03 +03:00
|
|
|
type objectPathResult struct {
|
2019-02-17 04:29:39 +03:00
|
|
|
part string
|
|
|
|
path string
|
|
|
|
pipe string
|
|
|
|
piped bool
|
|
|
|
wild bool
|
|
|
|
more bool
|
2016-08-25 20:05:03 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func parseObjectPath(path string) (r objectPathResult) {
|
2016-08-25 17:21:53 +03:00
|
|
|
for i := 0; i < len(path); i++ {
|
2019-06-29 22:10:41 +03:00
|
|
|
if path[i] == '|' {
|
|
|
|
r.part = path[:i]
|
|
|
|
r.pipe = path[i+1:]
|
|
|
|
r.piped = true
|
|
|
|
return
|
2019-02-17 04:29:39 +03:00
|
|
|
}
|
2016-08-25 17:21:53 +03:00
|
|
|
if path[i] == '.' {
|
Added Subselectors
It now possible to select multiple independent paths and join
their results into a single JSON document.
For example, given the following JSON
{
"info": {
"friends": [
{"first": "Dale", "last": "Murphy", "age": 44},
{"first": "Roger", "last": "Craig", "age": 68},
{"first": "Jane", "last": "Murphy", "age": 47}
]
}
}
The path `[info.friends.0.first,info.friends.1.last]` returns
["Dale","Craig"]
Or path `{info.friends.0.first,info.friends.1.last}` returns
{"first":"Dale","last":"Craig"}
You can also rename Object members such as
`{"alt1":info.friends.0.first,"alt2":info.friends.1.last}` returns
{"alt1":"Dale","alt2":"Craig"}
Finally you can combine this with any GJSON component
`info.friends.[0.first,1.age]` returns
["Dale",68]
This feature was request by @errashe in issue #113.
2019-06-30 01:23:32 +03:00
|
|
|
// peek at the next byte and see if it's a '@', '[', or '{'.
|
2016-08-25 20:05:03 +03:00
|
|
|
r.part = path[:i]
|
2019-06-29 22:10:41 +03:00
|
|
|
if !DisableModifiers &&
|
Added Subselectors
It now possible to select multiple independent paths and join
their results into a single JSON document.
For example, given the following JSON
{
"info": {
"friends": [
{"first": "Dale", "last": "Murphy", "age": 44},
{"first": "Roger", "last": "Craig", "age": 68},
{"first": "Jane", "last": "Murphy", "age": 47}
]
}
}
The path `[info.friends.0.first,info.friends.1.last]` returns
["Dale","Craig"]
Or path `{info.friends.0.first,info.friends.1.last}` returns
{"first":"Dale","last":"Craig"}
You can also rename Object members such as
`{"alt1":info.friends.0.first,"alt2":info.friends.1.last}` returns
{"alt1":"Dale","alt2":"Craig"}
Finally you can combine this with any GJSON component
`info.friends.[0.first,1.age]` returns
["Dale",68]
This feature was request by @errashe in issue #113.
2019-06-30 01:23:32 +03:00
|
|
|
i < len(path)-1 &&
|
|
|
|
(path[i+1] == '@' ||
|
|
|
|
path[i+1] == '[' || path[i+1] == '{') {
|
2019-06-28 04:27:53 +03:00
|
|
|
r.pipe = path[i+1:]
|
|
|
|
r.piped = true
|
|
|
|
} else {
|
|
|
|
r.path = path[i+1:]
|
|
|
|
r.more = true
|
|
|
|
}
|
2016-08-25 20:05:03 +03:00
|
|
|
return
|
2016-08-25 17:21:53 +03:00
|
|
|
}
|
2016-08-22 17:39:41 +03:00
|
|
|
if path[i] == '*' || path[i] == '?' {
|
2016-08-25 20:05:03 +03:00
|
|
|
r.wild = true
|
2016-08-22 17:39:41 +03:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
if path[i] == '\\' {
|
2016-08-21 15:35:33 +03:00
|
|
|
// go into escape mode. this is a slower path that
|
|
|
|
// strips off the escape character from the part.
|
2016-08-25 17:21:53 +03:00
|
|
|
epart := []byte(path[:i])
|
2016-08-21 15:35:33 +03:00
|
|
|
i++
|
|
|
|
if i < len(path) {
|
|
|
|
epart = append(epart, path[i])
|
2016-08-12 04:51:29 +03:00
|
|
|
i++
|
2016-08-21 15:35:33 +03:00
|
|
|
for ; i < len(path); i++ {
|
|
|
|
if path[i] == '\\' {
|
|
|
|
i++
|
|
|
|
if i < len(path) {
|
|
|
|
epart = append(epart, path[i])
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
} else if path[i] == '.' {
|
2016-08-25 20:05:03 +03:00
|
|
|
r.part = string(epart)
|
2019-06-28 04:27:53 +03:00
|
|
|
// peek at the next byte and see if it's a '@' modifier
|
2019-06-29 22:10:41 +03:00
|
|
|
if !DisableModifiers &&
|
2019-06-28 04:27:53 +03:00
|
|
|
i < len(path)-1 && path[i+1] == '@' {
|
|
|
|
r.pipe = path[i+1:]
|
|
|
|
r.piped = true
|
|
|
|
} else {
|
|
|
|
r.path = path[i+1:]
|
|
|
|
r.more = true
|
|
|
|
}
|
2016-08-25 20:05:03 +03:00
|
|
|
r.more = true
|
|
|
|
return
|
2019-06-28 03:51:42 +03:00
|
|
|
} else if path[i] == '|' {
|
|
|
|
r.part = string(epart)
|
|
|
|
r.pipe = path[i+1:]
|
|
|
|
r.piped = true
|
|
|
|
return
|
2016-08-21 15:35:33 +03:00
|
|
|
} else if path[i] == '*' || path[i] == '?' {
|
2016-08-25 20:05:03 +03:00
|
|
|
r.wild = true
|
2016-08-12 04:51:29 +03:00
|
|
|
}
|
2016-08-21 15:35:33 +03:00
|
|
|
epart = append(epart, path[i])
|
2016-08-12 04:51:29 +03:00
|
|
|
}
|
|
|
|
}
|
2016-08-21 15:35:33 +03:00
|
|
|
// append the last part
|
2016-08-25 20:05:03 +03:00
|
|
|
r.part = string(epart)
|
|
|
|
return
|
2016-08-11 20:39:38 +03:00
|
|
|
}
|
|
|
|
}
|
2016-08-25 20:05:03 +03:00
|
|
|
r.part = path
|
|
|
|
return
|
2016-08-25 17:21:53 +03:00
|
|
|
}
|
2016-08-11 20:39:38 +03:00
|
|
|
|
2016-08-25 19:40:50 +03:00
|
|
|
func parseSquash(json string, i int) (int, string) {
|
2019-11-01 15:08:48 +03:00
|
|
|
// expects that the lead character is a '[' or '{' or '('
|
2016-08-25 17:21:53 +03:00
|
|
|
// squash the value, ignoring all nested arrays and objects.
|
2019-11-01 15:08:48 +03:00
|
|
|
// the first '[' or '{' or '(' has already been read
|
2016-08-25 17:21:53 +03:00
|
|
|
s := i
|
|
|
|
i++
|
|
|
|
depth := 1
|
2016-08-11 06:07:45 +03:00
|
|
|
for ; i < len(json); i++ {
|
2016-08-25 17:21:53 +03:00
|
|
|
if json[i] >= '"' && json[i] <= '}' {
|
|
|
|
switch json[i] {
|
|
|
|
case '"':
|
2016-08-11 06:07:45 +03:00
|
|
|
i++
|
2016-08-25 17:21:53 +03:00
|
|
|
s2 := i
|
2016-08-11 06:07:45 +03:00
|
|
|
for ; i < len(json); i++ {
|
2016-08-21 17:17:11 +03:00
|
|
|
if json[i] > '\\' {
|
|
|
|
continue
|
|
|
|
}
|
2016-08-11 06:07:45 +03:00
|
|
|
if json[i] == '"' {
|
2016-08-25 17:21:53 +03:00
|
|
|
// look for an escaped slash
|
|
|
|
if json[i-1] == '\\' {
|
|
|
|
n := 0
|
|
|
|
for j := i - 2; j > s2-1; j-- {
|
|
|
|
if json[j] != '\\' {
|
|
|
|
break
|
2016-08-11 06:07:45 +03:00
|
|
|
}
|
2016-08-25 17:21:53 +03:00
|
|
|
n++
|
|
|
|
}
|
|
|
|
if n%2 == 0 {
|
|
|
|
continue
|
2016-08-11 06:07:45 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2019-11-01 15:08:48 +03:00
|
|
|
case '{', '[', '(':
|
2016-08-25 17:21:53 +03:00
|
|
|
depth++
|
2019-11-01 15:08:48 +03:00
|
|
|
case '}', ']', ')':
|
2016-08-25 17:21:53 +03:00
|
|
|
depth--
|
|
|
|
if depth == 0 {
|
|
|
|
i++
|
|
|
|
return i, json[s:i]
|
|
|
|
}
|
2016-08-11 06:07:45 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-08-25 17:21:53 +03:00
|
|
|
return i, json[s:]
|
|
|
|
}
|
2016-08-11 06:07:45 +03:00
|
|
|
|
2016-08-25 19:40:50 +03:00
|
|
|
func parseObject(c *parseContext, i int, path string) (int, bool) {
|
2016-08-30 17:21:20 +03:00
|
|
|
var pmatch, kesc, vesc, ok, hit bool
|
2016-08-25 17:21:53 +03:00
|
|
|
var key, val string
|
2016-08-25 20:05:03 +03:00
|
|
|
rp := parseObjectPath(path)
|
2019-02-17 04:29:39 +03:00
|
|
|
if !rp.more && rp.piped {
|
|
|
|
c.pipe = rp.pipe
|
|
|
|
c.piped = true
|
|
|
|
}
|
2016-08-25 19:40:50 +03:00
|
|
|
for i < len(c.json) {
|
|
|
|
for ; i < len(c.json); i++ {
|
|
|
|
if c.json[i] == '"' {
|
|
|
|
// parse_key_string
|
|
|
|
// this is slightly different from getting s string value
|
|
|
|
// because we don't need the outer quotes.
|
|
|
|
i++
|
|
|
|
var s = i
|
|
|
|
for ; i < len(c.json); i++ {
|
|
|
|
if c.json[i] > '\\' {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if c.json[i] == '"' {
|
|
|
|
i, key, kesc, ok = i+1, c.json[s:i], false, true
|
|
|
|
goto parse_key_string_done
|
|
|
|
}
|
|
|
|
if c.json[i] == '\\' {
|
|
|
|
i++
|
|
|
|
for ; i < len(c.json); i++ {
|
|
|
|
if c.json[i] > '\\' {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if c.json[i] == '"' {
|
|
|
|
// look for an escaped slash
|
|
|
|
if c.json[i-1] == '\\' {
|
|
|
|
n := 0
|
|
|
|
for j := i - 2; j > 0; j-- {
|
|
|
|
if c.json[j] != '\\' {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
n++
|
|
|
|
}
|
|
|
|
if n%2 == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
i, key, kesc, ok = i+1, c.json[s:i], true, true
|
|
|
|
goto parse_key_string_done
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2017-04-10 21:41:19 +03:00
|
|
|
key, kesc, ok = c.json[s:], false, false
|
2016-08-25 19:40:50 +03:00
|
|
|
parse_key_string_done:
|
2016-08-25 17:21:53 +03:00
|
|
|
break
|
|
|
|
}
|
2016-08-25 19:40:50 +03:00
|
|
|
if c.json[i] == '}' {
|
2016-08-25 17:21:53 +03:00
|
|
|
return i + 1, false
|
|
|
|
}
|
2016-08-12 04:51:29 +03:00
|
|
|
}
|
2016-08-25 17:21:53 +03:00
|
|
|
if !ok {
|
|
|
|
return i, false
|
|
|
|
}
|
2016-08-25 20:05:03 +03:00
|
|
|
if rp.wild {
|
2016-08-25 17:21:53 +03:00
|
|
|
if kesc {
|
2016-08-30 17:21:20 +03:00
|
|
|
pmatch = match.Match(unescape(key), rp.part)
|
2016-08-25 17:21:53 +03:00
|
|
|
} else {
|
2016-08-30 17:21:20 +03:00
|
|
|
pmatch = match.Match(key, rp.part)
|
2016-08-12 04:51:29 +03:00
|
|
|
}
|
2016-08-25 17:21:53 +03:00
|
|
|
} else {
|
|
|
|
if kesc {
|
2016-08-30 17:21:20 +03:00
|
|
|
pmatch = rp.part == unescape(key)
|
2016-08-25 17:21:53 +03:00
|
|
|
} else {
|
2016-08-30 17:21:20 +03:00
|
|
|
pmatch = rp.part == key
|
2016-08-25 17:21:53 +03:00
|
|
|
}
|
|
|
|
}
|
2016-08-30 17:21:20 +03:00
|
|
|
hit = pmatch && !rp.more
|
2016-08-25 19:40:50 +03:00
|
|
|
for ; i < len(c.json); i++ {
|
|
|
|
switch c.json[i] {
|
2016-08-25 17:21:53 +03:00
|
|
|
default:
|
|
|
|
continue
|
|
|
|
case '"':
|
|
|
|
i++
|
2016-08-25 19:40:50 +03:00
|
|
|
i, val, vesc, ok = parseString(c.json, i)
|
2016-08-25 17:21:53 +03:00
|
|
|
if !ok {
|
|
|
|
return i, false
|
|
|
|
}
|
|
|
|
if hit {
|
|
|
|
if vesc {
|
2016-08-25 19:40:50 +03:00
|
|
|
c.value.Str = unescape(val[1 : len(val)-1])
|
2016-08-25 17:21:53 +03:00
|
|
|
} else {
|
2016-08-25 19:40:50 +03:00
|
|
|
c.value.Str = val[1 : len(val)-1]
|
2016-08-19 23:51:52 +03:00
|
|
|
}
|
2016-08-25 19:40:50 +03:00
|
|
|
c.value.Raw = val
|
|
|
|
c.value.Type = String
|
2016-08-25 17:21:53 +03:00
|
|
|
return i, true
|
2016-08-19 23:51:52 +03:00
|
|
|
}
|
2016-08-25 17:21:53 +03:00
|
|
|
case '{':
|
2016-08-30 17:21:20 +03:00
|
|
|
if pmatch && !hit {
|
2016-08-25 20:05:03 +03:00
|
|
|
i, hit = parseObject(c, i+1, rp.path)
|
2016-08-25 17:21:53 +03:00
|
|
|
if hit {
|
|
|
|
return i, true
|
|
|
|
}
|
|
|
|
} else {
|
2016-08-25 19:40:50 +03:00
|
|
|
i, val = parseSquash(c.json, i)
|
2016-08-25 17:21:53 +03:00
|
|
|
if hit {
|
2016-08-25 19:40:50 +03:00
|
|
|
c.value.Raw = val
|
|
|
|
c.value.Type = JSON
|
2016-08-25 17:21:53 +03:00
|
|
|
return i, true
|
|
|
|
}
|
2016-08-19 23:51:52 +03:00
|
|
|
}
|
2016-08-25 17:21:53 +03:00
|
|
|
case '[':
|
2016-08-30 17:21:20 +03:00
|
|
|
if pmatch && !hit {
|
2016-08-25 20:05:03 +03:00
|
|
|
i, hit = parseArray(c, i+1, rp.path)
|
2016-08-25 17:21:53 +03:00
|
|
|
if hit {
|
|
|
|
return i, true
|
|
|
|
}
|
|
|
|
} else {
|
2016-08-25 19:40:50 +03:00
|
|
|
i, val = parseSquash(c.json, i)
|
2016-08-25 17:21:53 +03:00
|
|
|
if hit {
|
2016-08-25 19:40:50 +03:00
|
|
|
c.value.Raw = val
|
|
|
|
c.value.Type = JSON
|
2016-08-25 17:21:53 +03:00
|
|
|
return i, true
|
|
|
|
}
|
2016-08-11 06:07:45 +03:00
|
|
|
}
|
2016-08-25 17:21:53 +03:00
|
|
|
case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
2016-08-25 19:40:50 +03:00
|
|
|
i, val = parseNumber(c.json, i)
|
2016-08-25 17:21:53 +03:00
|
|
|
if hit {
|
2016-08-25 19:40:50 +03:00
|
|
|
c.value.Raw = val
|
|
|
|
c.value.Type = Number
|
|
|
|
c.value.Num, _ = strconv.ParseFloat(val, 64)
|
2016-08-25 17:21:53 +03:00
|
|
|
return i, true
|
|
|
|
}
|
|
|
|
case 't', 'f', 'n':
|
2016-08-25 19:40:50 +03:00
|
|
|
vc := c.json[i]
|
|
|
|
i, val = parseLiteral(c.json, i)
|
2016-08-25 17:21:53 +03:00
|
|
|
if hit {
|
2016-08-25 19:40:50 +03:00
|
|
|
c.value.Raw = val
|
2016-08-25 17:21:53 +03:00
|
|
|
switch vc {
|
|
|
|
case 't':
|
2016-08-25 19:40:50 +03:00
|
|
|
c.value.Type = True
|
2016-08-25 17:21:53 +03:00
|
|
|
case 'f':
|
2016-08-25 19:40:50 +03:00
|
|
|
c.value.Type = False
|
2016-08-25 17:21:53 +03:00
|
|
|
}
|
|
|
|
return i, true
|
2016-08-12 04:51:29 +03:00
|
|
|
}
|
|
|
|
}
|
2016-08-25 17:21:53 +03:00
|
|
|
break
|
|
|
|
}
|
2016-08-11 06:07:45 +03:00
|
|
|
}
|
2016-08-25 17:21:53 +03:00
|
|
|
return i, false
|
|
|
|
}
|
2016-08-31 23:23:20 +03:00
|
|
|
func queryMatches(rp *arrayPathResult, value Result) bool {
|
|
|
|
rpv := rp.query.value
|
|
|
|
if len(rpv) > 2 && rpv[0] == '"' && rpv[len(rpv)-1] == '"' {
|
|
|
|
rpv = rpv[1 : len(rpv)-1]
|
|
|
|
}
|
2019-07-12 16:14:45 +03:00
|
|
|
if !value.Exists() {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if rp.query.op == "" {
|
|
|
|
// the query is only looking for existence, such as:
|
|
|
|
// friends.#(name)
|
|
|
|
// which makes sure that the array "friends" has an element of
|
|
|
|
// "name" that exists
|
|
|
|
return true
|
|
|
|
}
|
2016-08-31 23:23:20 +03:00
|
|
|
switch value.Type {
|
|
|
|
case String:
|
|
|
|
switch rp.query.op {
|
|
|
|
case "=":
|
|
|
|
return value.Str == rpv
|
2016-12-01 00:38:08 +03:00
|
|
|
case "!=":
|
|
|
|
return value.Str != rpv
|
2016-08-31 23:23:20 +03:00
|
|
|
case "<":
|
|
|
|
return value.Str < rpv
|
|
|
|
case "<=":
|
|
|
|
return value.Str <= rpv
|
|
|
|
case ">":
|
|
|
|
return value.Str > rpv
|
|
|
|
case ">=":
|
|
|
|
return value.Str >= rpv
|
2016-12-01 00:32:17 +03:00
|
|
|
case "%":
|
|
|
|
return match.Match(value.Str, rpv)
|
2018-10-28 02:20:01 +03:00
|
|
|
case "!%":
|
|
|
|
return !match.Match(value.Str, rpv)
|
2016-08-31 23:23:20 +03:00
|
|
|
}
|
|
|
|
case Number:
|
|
|
|
rpvn, _ := strconv.ParseFloat(rpv, 64)
|
|
|
|
switch rp.query.op {
|
|
|
|
case "=":
|
|
|
|
return value.Num == rpvn
|
2016-12-01 00:38:08 +03:00
|
|
|
case "!=":
|
2018-01-23 15:45:05 +03:00
|
|
|
return value.Num != rpvn
|
2016-08-31 23:23:20 +03:00
|
|
|
case "<":
|
|
|
|
return value.Num < rpvn
|
|
|
|
case "<=":
|
|
|
|
return value.Num <= rpvn
|
|
|
|
case ">":
|
|
|
|
return value.Num > rpvn
|
|
|
|
case ">=":
|
|
|
|
return value.Num >= rpvn
|
|
|
|
}
|
|
|
|
case True:
|
|
|
|
switch rp.query.op {
|
|
|
|
case "=":
|
|
|
|
return rpv == "true"
|
2016-12-01 00:38:08 +03:00
|
|
|
case "!=":
|
|
|
|
return rpv != "true"
|
2016-08-31 23:23:20 +03:00
|
|
|
case ">":
|
|
|
|
return rpv == "false"
|
|
|
|
case ">=":
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
case False:
|
|
|
|
switch rp.query.op {
|
|
|
|
case "=":
|
|
|
|
return rpv == "false"
|
2016-12-01 00:38:08 +03:00
|
|
|
case "!=":
|
|
|
|
return rpv != "false"
|
2016-08-31 23:23:20 +03:00
|
|
|
case "<":
|
|
|
|
return rpv == "true"
|
|
|
|
case "<=":
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
2016-08-25 19:40:50 +03:00
|
|
|
func parseArray(c *parseContext, i int, path string) (int, bool) {
|
2016-08-30 17:21:20 +03:00
|
|
|
var pmatch, vesc, ok, hit bool
|
2016-08-25 17:21:53 +03:00
|
|
|
var val string
|
|
|
|
var h int
|
|
|
|
var alog []int
|
|
|
|
var partidx int
|
2016-11-30 17:52:25 +03:00
|
|
|
var multires []byte
|
2016-08-25 20:05:03 +03:00
|
|
|
rp := parseArrayPath(path)
|
|
|
|
if !rp.arrch {
|
2017-05-26 05:39:18 +03:00
|
|
|
n, ok := parseUint(rp.part)
|
|
|
|
if !ok {
|
2016-08-25 17:21:53 +03:00
|
|
|
partidx = -1
|
|
|
|
} else {
|
|
|
|
partidx = int(n)
|
|
|
|
}
|
|
|
|
}
|
2019-02-17 04:29:39 +03:00
|
|
|
if !rp.more && rp.piped {
|
|
|
|
c.pipe = rp.pipe
|
|
|
|
c.piped = true
|
|
|
|
}
|
2019-06-28 15:55:18 +03:00
|
|
|
|
|
|
|
procQuery := func(qval Result) bool {
|
|
|
|
if rp.query.all {
|
|
|
|
if len(multires) == 0 {
|
|
|
|
multires = append(multires, '[')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
var res Result
|
|
|
|
if qval.Type == JSON {
|
|
|
|
res = qval.Get(rp.query.path)
|
|
|
|
} else {
|
2019-06-28 20:18:23 +03:00
|
|
|
if rp.query.path != "" {
|
2019-06-28 15:55:18 +03:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
res = qval
|
|
|
|
}
|
|
|
|
if queryMatches(&rp, res) {
|
|
|
|
if rp.more {
|
|
|
|
left, right, ok := splitPossiblePipe(rp.path)
|
|
|
|
if ok {
|
|
|
|
rp.path = left
|
|
|
|
c.pipe = right
|
|
|
|
c.piped = true
|
|
|
|
}
|
|
|
|
res = qval.Get(rp.path)
|
|
|
|
} else {
|
|
|
|
res = qval
|
|
|
|
}
|
|
|
|
if rp.query.all {
|
|
|
|
raw := res.Raw
|
|
|
|
if len(raw) == 0 {
|
|
|
|
raw = res.String()
|
|
|
|
}
|
|
|
|
if raw != "" {
|
|
|
|
if len(multires) > 1 {
|
|
|
|
multires = append(multires, ',')
|
|
|
|
}
|
|
|
|
multires = append(multires, raw...)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
c.value = res
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
Added support for JSON Lines
Added support for JSON Lines (http://jsonlines.org) using the `..` prefix.
Which when specified, treats the multi-lined document as an array.
For example:
```
{"name": "Gilbert", "age": 61}
{"name": "Alexa", "age": 34}
{"name": "May", "age": 57}
{"name": "Deloise", "age": 44}
```
```
..# >> 4
..1 >> {"name": "Alexa", "age": 34}
..3 >> {"name": "Deloise", "age": 44}
..#.name >> ["Gilbert","Alexa","May","Deloise"]
..#[name="May"].age >> 57
```
Closes #60
2018-02-10 01:41:16 +03:00
|
|
|
for i < len(c.json)+1 {
|
2016-08-25 20:05:03 +03:00
|
|
|
if !rp.arrch {
|
2016-08-30 17:21:20 +03:00
|
|
|
pmatch = partidx == h
|
|
|
|
hit = pmatch && !rp.more
|
2016-08-25 17:21:53 +03:00
|
|
|
}
|
|
|
|
h++
|
2016-08-25 20:05:03 +03:00
|
|
|
if rp.alogok {
|
2016-08-25 17:21:53 +03:00
|
|
|
alog = append(alog, i)
|
|
|
|
}
|
Added support for JSON Lines
Added support for JSON Lines (http://jsonlines.org) using the `..` prefix.
Which when specified, treats the multi-lined document as an array.
For example:
```
{"name": "Gilbert", "age": 61}
{"name": "Alexa", "age": 34}
{"name": "May", "age": 57}
{"name": "Deloise", "age": 44}
```
```
..# >> 4
..1 >> {"name": "Alexa", "age": 34}
..3 >> {"name": "Deloise", "age": 44}
..#.name >> ["Gilbert","Alexa","May","Deloise"]
..#[name="May"].age >> 57
```
Closes #60
2018-02-10 01:41:16 +03:00
|
|
|
for ; ; i++ {
|
|
|
|
var ch byte
|
|
|
|
if i > len(c.json) {
|
|
|
|
break
|
|
|
|
} else if i == len(c.json) {
|
|
|
|
ch = ']'
|
|
|
|
} else {
|
|
|
|
ch = c.json[i]
|
|
|
|
}
|
|
|
|
switch ch {
|
2016-08-25 17:21:53 +03:00
|
|
|
default:
|
|
|
|
continue
|
|
|
|
case '"':
|
|
|
|
i++
|
2016-08-25 19:40:50 +03:00
|
|
|
i, val, vesc, ok = parseString(c.json, i)
|
2016-08-25 17:21:53 +03:00
|
|
|
if !ok {
|
|
|
|
return i, false
|
|
|
|
}
|
2019-06-28 15:55:18 +03:00
|
|
|
if rp.query.on {
|
|
|
|
var qval Result
|
|
|
|
if vesc {
|
|
|
|
qval.Str = unescape(val[1 : len(val)-1])
|
|
|
|
} else {
|
|
|
|
qval.Str = val[1 : len(val)-1]
|
|
|
|
}
|
|
|
|
qval.Raw = val
|
|
|
|
qval.Type = String
|
|
|
|
if procQuery(qval) {
|
|
|
|
return i, true
|
|
|
|
}
|
|
|
|
} else if hit {
|
2016-08-25 20:05:03 +03:00
|
|
|
if rp.alogok {
|
2016-08-25 17:21:53 +03:00
|
|
|
break
|
|
|
|
}
|
|
|
|
if vesc {
|
2016-08-25 19:40:50 +03:00
|
|
|
c.value.Str = unescape(val[1 : len(val)-1])
|
2016-08-25 17:21:53 +03:00
|
|
|
} else {
|
2016-08-25 19:40:50 +03:00
|
|
|
c.value.Str = val[1 : len(val)-1]
|
2016-08-25 17:21:53 +03:00
|
|
|
}
|
2016-08-25 19:40:50 +03:00
|
|
|
c.value.Raw = val
|
|
|
|
c.value.Type = String
|
2016-08-25 17:21:53 +03:00
|
|
|
return i, true
|
|
|
|
}
|
|
|
|
case '{':
|
2016-08-30 17:21:20 +03:00
|
|
|
if pmatch && !hit {
|
2016-08-25 20:05:03 +03:00
|
|
|
i, hit = parseObject(c, i+1, rp.path)
|
2016-08-25 17:21:53 +03:00
|
|
|
if hit {
|
2016-08-25 20:05:03 +03:00
|
|
|
if rp.alogok {
|
2016-08-25 17:21:53 +03:00
|
|
|
break
|
2016-08-21 17:17:11 +03:00
|
|
|
}
|
2016-08-25 17:21:53 +03:00
|
|
|
return i, true
|
|
|
|
}
|
|
|
|
} else {
|
2016-08-25 19:40:50 +03:00
|
|
|
i, val = parseSquash(c.json, i)
|
2016-08-31 23:23:20 +03:00
|
|
|
if rp.query.on {
|
2019-06-28 15:55:18 +03:00
|
|
|
if procQuery(Result{Raw: val, Type: JSON}) {
|
|
|
|
return i, true
|
2016-08-31 23:23:20 +03:00
|
|
|
}
|
|
|
|
} else if hit {
|
2016-08-25 20:05:03 +03:00
|
|
|
if rp.alogok {
|
2016-08-12 04:51:29 +03:00
|
|
|
break
|
2016-08-11 06:07:45 +03:00
|
|
|
}
|
2016-08-25 19:40:50 +03:00
|
|
|
c.value.Raw = val
|
|
|
|
c.value.Type = JSON
|
2016-08-25 17:21:53 +03:00
|
|
|
return i, true
|
2016-08-12 04:51:29 +03:00
|
|
|
}
|
2016-08-25 17:21:53 +03:00
|
|
|
}
|
|
|
|
case '[':
|
2016-08-30 17:21:20 +03:00
|
|
|
if pmatch && !hit {
|
2016-08-25 20:05:03 +03:00
|
|
|
i, hit = parseArray(c, i+1, rp.path)
|
2016-08-25 17:21:53 +03:00
|
|
|
if hit {
|
2016-08-25 20:05:03 +03:00
|
|
|
if rp.alogok {
|
2016-08-25 17:21:53 +03:00
|
|
|
break
|
|
|
|
}
|
|
|
|
return i, true
|
|
|
|
}
|
|
|
|
} else {
|
2016-08-25 19:40:50 +03:00
|
|
|
i, val = parseSquash(c.json, i)
|
2019-06-28 15:55:18 +03:00
|
|
|
if rp.query.on {
|
|
|
|
if procQuery(Result{Raw: val, Type: JSON}) {
|
|
|
|
return i, true
|
|
|
|
}
|
|
|
|
} else if hit {
|
2016-08-25 20:05:03 +03:00
|
|
|
if rp.alogok {
|
2016-08-25 17:21:53 +03:00
|
|
|
break
|
|
|
|
}
|
2016-08-25 19:40:50 +03:00
|
|
|
c.value.Raw = val
|
|
|
|
c.value.Type = JSON
|
2016-08-25 17:21:53 +03:00
|
|
|
return i, true
|
2016-08-11 06:07:45 +03:00
|
|
|
}
|
|
|
|
}
|
2016-08-25 17:21:53 +03:00
|
|
|
case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
2016-08-25 19:40:50 +03:00
|
|
|
i, val = parseNumber(c.json, i)
|
2019-06-28 15:55:18 +03:00
|
|
|
if rp.query.on {
|
|
|
|
var qval Result
|
|
|
|
qval.Raw = val
|
|
|
|
qval.Type = Number
|
|
|
|
qval.Num, _ = strconv.ParseFloat(val, 64)
|
|
|
|
if procQuery(qval) {
|
|
|
|
return i, true
|
|
|
|
}
|
|
|
|
} else if hit {
|
2016-08-25 20:05:03 +03:00
|
|
|
if rp.alogok {
|
2016-08-25 17:21:53 +03:00
|
|
|
break
|
2016-08-22 16:05:51 +03:00
|
|
|
}
|
2016-08-25 19:40:50 +03:00
|
|
|
c.value.Raw = val
|
|
|
|
c.value.Type = Number
|
|
|
|
c.value.Num, _ = strconv.ParseFloat(val, 64)
|
2016-08-25 17:21:53 +03:00
|
|
|
return i, true
|
|
|
|
}
|
|
|
|
case 't', 'f', 'n':
|
2016-08-25 19:40:50 +03:00
|
|
|
vc := c.json[i]
|
|
|
|
i, val = parseLiteral(c.json, i)
|
2019-06-28 15:55:18 +03:00
|
|
|
if rp.query.on {
|
|
|
|
var qval Result
|
|
|
|
qval.Raw = val
|
|
|
|
switch vc {
|
|
|
|
case 't':
|
|
|
|
qval.Type = True
|
|
|
|
case 'f':
|
|
|
|
qval.Type = False
|
|
|
|
}
|
|
|
|
if procQuery(qval) {
|
|
|
|
return i, true
|
|
|
|
}
|
|
|
|
} else if hit {
|
2016-08-25 20:05:03 +03:00
|
|
|
if rp.alogok {
|
2016-08-11 06:07:45 +03:00
|
|
|
break
|
|
|
|
}
|
2016-08-25 19:40:50 +03:00
|
|
|
c.value.Raw = val
|
2016-08-25 17:21:53 +03:00
|
|
|
switch vc {
|
|
|
|
case 't':
|
2016-08-25 19:40:50 +03:00
|
|
|
c.value.Type = True
|
2016-08-25 17:21:53 +03:00
|
|
|
case 'f':
|
2016-08-25 19:40:50 +03:00
|
|
|
c.value.Type = False
|
2016-08-25 17:21:53 +03:00
|
|
|
}
|
|
|
|
return i, true
|
|
|
|
}
|
|
|
|
case ']':
|
2016-08-25 20:05:03 +03:00
|
|
|
if rp.arrch && rp.part == "#" {
|
|
|
|
if rp.alogok {
|
2019-06-28 03:51:42 +03:00
|
|
|
left, right, ok := splitPossiblePipe(rp.alogkey)
|
|
|
|
if ok {
|
|
|
|
rp.alogkey = left
|
|
|
|
c.pipe = right
|
|
|
|
c.piped = true
|
|
|
|
}
|
2016-08-25 17:21:53 +03:00
|
|
|
var jsons = make([]byte, 0, 64)
|
|
|
|
jsons = append(jsons, '[')
|
2016-11-30 04:08:02 +03:00
|
|
|
for j, k := 0, 0; j < len(alog); j++ {
|
2019-11-03 00:52:36 +03:00
|
|
|
idx := alog[j]
|
|
|
|
for idx < len(c.json) {
|
|
|
|
switch c.json[idx] {
|
|
|
|
case ' ', '\t', '\r', '\n':
|
|
|
|
idx++
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if idx < len(c.json) && c.json[idx] != ']' {
|
|
|
|
_, res, ok := parseAny(c.json, idx, true)
|
|
|
|
if ok {
|
|
|
|
res := res.Get(rp.alogkey)
|
|
|
|
if res.Exists() {
|
|
|
|
if k > 0 {
|
|
|
|
jsons = append(jsons, ',')
|
|
|
|
}
|
|
|
|
raw := res.Raw
|
|
|
|
if len(raw) == 0 {
|
|
|
|
raw = res.String()
|
|
|
|
}
|
|
|
|
jsons = append(jsons, []byte(raw)...)
|
|
|
|
k++
|
2019-06-28 01:50:15 +03:00
|
|
|
}
|
2016-08-11 06:07:45 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-08-25 17:21:53 +03:00
|
|
|
jsons = append(jsons, ']')
|
2016-08-25 19:40:50 +03:00
|
|
|
c.value.Type = JSON
|
|
|
|
c.value.Raw = string(jsons)
|
2016-08-25 17:21:53 +03:00
|
|
|
return i + 1, true
|
2016-08-11 06:07:45 +03:00
|
|
|
}
|
2017-04-10 21:41:19 +03:00
|
|
|
if rp.alogok {
|
|
|
|
break
|
|
|
|
}
|
2019-06-30 14:48:53 +03:00
|
|
|
|
2017-04-10 21:41:19 +03:00
|
|
|
c.value.Type = Number
|
|
|
|
c.value.Num = float64(h - 1)
|
2019-06-30 14:48:53 +03:00
|
|
|
c.value.Raw = strconv.Itoa(h - 1)
|
2017-04-10 21:41:19 +03:00
|
|
|
c.calcd = true
|
|
|
|
return i + 1, true
|
2016-08-11 06:07:45 +03:00
|
|
|
}
|
2016-11-30 17:52:25 +03:00
|
|
|
if len(multires) > 0 && !c.value.Exists() {
|
|
|
|
c.value = Result{
|
|
|
|
Raw: string(append(multires, ']')),
|
|
|
|
Type: JSON,
|
|
|
|
}
|
|
|
|
}
|
2016-08-25 17:21:53 +03:00
|
|
|
return i + 1, false
|
2016-08-11 06:07:45 +03:00
|
|
|
}
|
2016-08-25 17:21:53 +03:00
|
|
|
break
|
2016-08-11 06:07:45 +03:00
|
|
|
}
|
|
|
|
}
|
2016-08-25 17:21:53 +03:00
|
|
|
return i, false
|
|
|
|
}
|
2016-08-11 06:07:45 +03:00
|
|
|
|
2019-06-28 03:51:42 +03:00
|
|
|
func splitPossiblePipe(path string) (left, right string, ok bool) {
|
|
|
|
// take a quick peek for the pipe character. If found we'll split the piped
|
|
|
|
// part of the path into the c.pipe field and shorten the rp.
|
|
|
|
var possible bool
|
|
|
|
for i := 0; i < len(path); i++ {
|
|
|
|
if path[i] == '|' {
|
|
|
|
possible = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !possible {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-11-01 15:08:48 +03:00
|
|
|
if len(path) > 0 && path[0] == '{' {
|
|
|
|
squashed := squash(path[1:])
|
|
|
|
if len(squashed) < len(path)-1 {
|
|
|
|
squashed = path[:len(squashed)+1]
|
|
|
|
remain := path[len(squashed):]
|
|
|
|
if remain[0] == '|' {
|
|
|
|
return squashed, remain[1:], true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-06-28 03:51:42 +03:00
|
|
|
// split the left and right side of the path with the pipe character as
|
|
|
|
// the delimiter. This is a little tricky because we'll need to basically
|
|
|
|
// parse the entire path.
|
|
|
|
for i := 0; i < len(path); i++ {
|
|
|
|
if path[i] == '\\' {
|
|
|
|
i++
|
|
|
|
} else if path[i] == '.' {
|
|
|
|
if i == len(path)-1 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if path[i+1] == '#' {
|
|
|
|
i += 2
|
|
|
|
if i == len(path) {
|
|
|
|
return
|
|
|
|
}
|
2019-06-29 21:31:27 +03:00
|
|
|
if path[i] == '[' || path[i] == '(' {
|
|
|
|
var start, end byte
|
|
|
|
if path[i] == '[' {
|
|
|
|
start, end = '[', ']'
|
|
|
|
} else {
|
|
|
|
start, end = '(', ')'
|
|
|
|
}
|
2019-06-28 03:51:42 +03:00
|
|
|
// inside selector, balance brackets
|
|
|
|
i++
|
|
|
|
depth := 1
|
|
|
|
for ; i < len(path); i++ {
|
|
|
|
if path[i] == '\\' {
|
|
|
|
i++
|
2019-06-29 21:31:27 +03:00
|
|
|
} else if path[i] == start {
|
2019-06-28 03:51:42 +03:00
|
|
|
depth++
|
2019-06-29 21:31:27 +03:00
|
|
|
} else if path[i] == end {
|
2019-06-28 03:51:42 +03:00
|
|
|
depth--
|
|
|
|
if depth == 0 {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
} else if path[i] == '"' {
|
|
|
|
// inside selector string, balance quotes
|
|
|
|
i++
|
|
|
|
for ; i < len(path); i++ {
|
|
|
|
if path[i] == '\\' {
|
|
|
|
i++
|
|
|
|
} else if path[i] == '"' {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if path[i] == '|' {
|
|
|
|
return path[:i], path[i+1:], true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
Added support for JSON Lines
Added support for JSON Lines (http://jsonlines.org) using the `..` prefix.
Which when specified, treats the multi-lined document as an array.
For example:
```
{"name": "Gilbert", "age": 61}
{"name": "Alexa", "age": 34}
{"name": "May", "age": 57}
{"name": "Deloise", "age": 44}
```
```
..# >> 4
..1 >> {"name": "Alexa", "age": 34}
..3 >> {"name": "Deloise", "age": 44}
..#.name >> ["Gilbert","Alexa","May","Deloise"]
..#[name="May"].age >> 57
```
Closes #60
2018-02-10 01:41:16 +03:00
|
|
|
// ForEachLine iterates through lines of JSON as specified by the JSON Lines
|
|
|
|
// format (http://jsonlines.org/).
|
|
|
|
// Each line is returned as a GJSON Result.
|
|
|
|
func ForEachLine(json string, iterator func(line Result) bool) {
|
|
|
|
var res Result
|
|
|
|
var i int
|
|
|
|
for {
|
|
|
|
i, res, _ = parseAny(json, i, true)
|
|
|
|
if !res.Exists() {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if !iterator(res) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Added Subselectors
It now possible to select multiple independent paths and join
their results into a single JSON document.
For example, given the following JSON
{
"info": {
"friends": [
{"first": "Dale", "last": "Murphy", "age": 44},
{"first": "Roger", "last": "Craig", "age": 68},
{"first": "Jane", "last": "Murphy", "age": 47}
]
}
}
The path `[info.friends.0.first,info.friends.1.last]` returns
["Dale","Craig"]
Or path `{info.friends.0.first,info.friends.1.last}` returns
{"first":"Dale","last":"Craig"}
You can also rename Object members such as
`{"alt1":info.friends.0.first,"alt2":info.friends.1.last}` returns
{"alt1":"Dale","alt2":"Craig"}
Finally you can combine this with any GJSON component
`info.friends.[0.first,1.age]` returns
["Dale",68]
This feature was request by @errashe in issue #113.
2019-06-30 01:23:32 +03:00
|
|
|
type subSelector struct {
|
|
|
|
name string
|
|
|
|
path string
|
|
|
|
}
|
|
|
|
|
|
|
|
// parseSubSelectors returns the subselectors belonging to a '[path1,path2]' or
|
|
|
|
// '{"field1":path1,"field2":path2}' type subSelection. It's expected that the
|
|
|
|
// first character in path is either '[' or '{', and has already been checked
|
|
|
|
// prior to calling this function.
|
|
|
|
func parseSubSelectors(path string) (sels []subSelector, out string, ok bool) {
|
2019-11-01 15:08:48 +03:00
|
|
|
modifer := 0
|
Added Subselectors
It now possible to select multiple independent paths and join
their results into a single JSON document.
For example, given the following JSON
{
"info": {
"friends": [
{"first": "Dale", "last": "Murphy", "age": 44},
{"first": "Roger", "last": "Craig", "age": 68},
{"first": "Jane", "last": "Murphy", "age": 47}
]
}
}
The path `[info.friends.0.first,info.friends.1.last]` returns
["Dale","Craig"]
Or path `{info.friends.0.first,info.friends.1.last}` returns
{"first":"Dale","last":"Craig"}
You can also rename Object members such as
`{"alt1":info.friends.0.first,"alt2":info.friends.1.last}` returns
{"alt1":"Dale","alt2":"Craig"}
Finally you can combine this with any GJSON component
`info.friends.[0.first,1.age]` returns
["Dale",68]
This feature was request by @errashe in issue #113.
2019-06-30 01:23:32 +03:00
|
|
|
depth := 1
|
|
|
|
colon := 0
|
|
|
|
start := 1
|
|
|
|
i := 1
|
|
|
|
pushSel := func() {
|
|
|
|
var sel subSelector
|
|
|
|
if colon == 0 {
|
|
|
|
sel.path = path[start:i]
|
|
|
|
} else {
|
|
|
|
sel.name = path[start:colon]
|
|
|
|
sel.path = path[colon+1 : i]
|
|
|
|
}
|
|
|
|
sels = append(sels, sel)
|
|
|
|
colon = 0
|
|
|
|
start = i + 1
|
|
|
|
}
|
|
|
|
for ; i < len(path); i++ {
|
|
|
|
switch path[i] {
|
|
|
|
case '\\':
|
|
|
|
i++
|
2019-11-01 15:08:48 +03:00
|
|
|
case '@':
|
|
|
|
if modifer == 0 && i > 0 && (path[i-1] == '.' || path[i-1] == '|') {
|
|
|
|
modifer = i
|
|
|
|
}
|
Added Subselectors
It now possible to select multiple independent paths and join
their results into a single JSON document.
For example, given the following JSON
{
"info": {
"friends": [
{"first": "Dale", "last": "Murphy", "age": 44},
{"first": "Roger", "last": "Craig", "age": 68},
{"first": "Jane", "last": "Murphy", "age": 47}
]
}
}
The path `[info.friends.0.first,info.friends.1.last]` returns
["Dale","Craig"]
Or path `{info.friends.0.first,info.friends.1.last}` returns
{"first":"Dale","last":"Craig"}
You can also rename Object members such as
`{"alt1":info.friends.0.first,"alt2":info.friends.1.last}` returns
{"alt1":"Dale","alt2":"Craig"}
Finally you can combine this with any GJSON component
`info.friends.[0.first,1.age]` returns
["Dale",68]
This feature was request by @errashe in issue #113.
2019-06-30 01:23:32 +03:00
|
|
|
case ':':
|
2019-11-01 15:08:48 +03:00
|
|
|
if modifer == 0 && colon == 0 && depth == 1 {
|
Added Subselectors
It now possible to select multiple independent paths and join
their results into a single JSON document.
For example, given the following JSON
{
"info": {
"friends": [
{"first": "Dale", "last": "Murphy", "age": 44},
{"first": "Roger", "last": "Craig", "age": 68},
{"first": "Jane", "last": "Murphy", "age": 47}
]
}
}
The path `[info.friends.0.first,info.friends.1.last]` returns
["Dale","Craig"]
Or path `{info.friends.0.first,info.friends.1.last}` returns
{"first":"Dale","last":"Craig"}
You can also rename Object members such as
`{"alt1":info.friends.0.first,"alt2":info.friends.1.last}` returns
{"alt1":"Dale","alt2":"Craig"}
Finally you can combine this with any GJSON component
`info.friends.[0.first,1.age]` returns
["Dale",68]
This feature was request by @errashe in issue #113.
2019-06-30 01:23:32 +03:00
|
|
|
colon = i
|
|
|
|
}
|
|
|
|
case ',':
|
|
|
|
if depth == 1 {
|
|
|
|
pushSel()
|
|
|
|
}
|
|
|
|
case '"':
|
|
|
|
i++
|
|
|
|
loop:
|
|
|
|
for ; i < len(path); i++ {
|
|
|
|
switch path[i] {
|
|
|
|
case '\\':
|
|
|
|
i++
|
|
|
|
case '"':
|
|
|
|
break loop
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case '[', '(', '{':
|
|
|
|
depth++
|
|
|
|
case ']', ')', '}':
|
|
|
|
depth--
|
|
|
|
if depth == 0 {
|
|
|
|
pushSel()
|
|
|
|
path = path[i+1:]
|
|
|
|
return sels, path, true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// nameOfLast returns the name of the last component
|
|
|
|
func nameOfLast(path string) string {
|
|
|
|
for i := len(path) - 1; i >= 0; i-- {
|
|
|
|
if path[i] == '|' || path[i] == '.' {
|
|
|
|
if i > 0 {
|
|
|
|
if path[i-1] == '\\' {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return path[i+1:]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return path
|
|
|
|
}
|
|
|
|
|
|
|
|
func isSimpleName(component string) bool {
|
|
|
|
for i := 0; i < len(component); i++ {
|
|
|
|
if component[i] < ' ' {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
switch component[i] {
|
|
|
|
case '[', ']', '{', '}', '(', ')', '#', '|':
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func appendJSONString(dst []byte, s string) []byte {
|
|
|
|
for i := 0; i < len(s); i++ {
|
|
|
|
if s[i] < ' ' || s[i] == '\\' || s[i] == '"' || s[i] > 126 {
|
|
|
|
d, _ := json.Marshal(s)
|
|
|
|
return append(dst, string(d)...)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
dst = append(dst, '"')
|
|
|
|
dst = append(dst, s...)
|
|
|
|
dst = append(dst, '"')
|
|
|
|
return dst
|
|
|
|
}
|
|
|
|
|
2016-09-08 18:08:53 +03:00
|
|
|
type parseContext struct {
|
|
|
|
json string
|
|
|
|
value Result
|
2019-02-17 04:29:39 +03:00
|
|
|
pipe string
|
|
|
|
piped bool
|
2016-12-08 02:37:33 +03:00
|
|
|
calcd bool
|
Added support for JSON Lines
Added support for JSON Lines (http://jsonlines.org) using the `..` prefix.
Which when specified, treats the multi-lined document as an array.
For example:
```
{"name": "Gilbert", "age": 61}
{"name": "Alexa", "age": 34}
{"name": "May", "age": 57}
{"name": "Deloise", "age": 44}
```
```
..# >> 4
..1 >> {"name": "Alexa", "age": 34}
..3 >> {"name": "Deloise", "age": 44}
..#.name >> ["Gilbert","Alexa","May","Deloise"]
..#[name="May"].age >> 57
```
Closes #60
2018-02-10 01:41:16 +03:00
|
|
|
lines bool
|
2016-09-08 18:08:53 +03:00
|
|
|
}
|
|
|
|
|
2016-08-25 17:21:53 +03:00
|
|
|
// Get searches json for the specified path.
|
|
|
|
// A path is in dot syntax, such as "name.last" or "age".
|
|
|
|
// When the value is found it's returned immediately.
|
|
|
|
//
|
2020-04-20 14:10:36 +03:00
|
|
|
// A path is a series of keys separated by a dot.
|
2016-08-25 17:21:53 +03:00
|
|
|
// A key may contain special wildcard characters '*' and '?'.
|
|
|
|
// To access an array value use the index as the key.
|
2019-06-28 04:03:36 +03:00
|
|
|
// To get the number of elements in an array or to access a child path, use
|
|
|
|
// the '#' character.
|
2016-08-25 17:21:53 +03:00
|
|
|
// The dot and wildcard character can be escaped with '\'.
|
|
|
|
//
|
|
|
|
// {
|
|
|
|
// "name": {"first": "Tom", "last": "Anderson"},
|
|
|
|
// "age":37,
|
|
|
|
// "children": ["Sara","Alex","Jack"],
|
|
|
|
// "friends": [
|
|
|
|
// {"first": "James", "last": "Murphy"},
|
|
|
|
// {"first": "Roger", "last": "Craig"}
|
|
|
|
// ]
|
|
|
|
// }
|
|
|
|
// "name.last" >> "Anderson"
|
|
|
|
// "age" >> 37
|
2016-11-02 23:45:25 +03:00
|
|
|
// "children" >> ["Sara","Alex","Jack"]
|
2016-08-25 17:21:53 +03:00
|
|
|
// "children.#" >> 3
|
|
|
|
// "children.1" >> "Alex"
|
|
|
|
// "child*.2" >> "Jack"
|
|
|
|
// "c?ildren.0" >> "Sara"
|
2016-11-02 23:45:25 +03:00
|
|
|
// "friends.#.first" >> ["James","Roger"]
|
2016-08-25 17:21:53 +03:00
|
|
|
//
|
2018-02-18 20:05:23 +03:00
|
|
|
// This function expects that the json is well-formed, and does not validate.
|
|
|
|
// Invalid json will not panic, but it may return back unexpected results.
|
|
|
|
// If you are consuming JSON from an unpredictable source then you may want to
|
|
|
|
// use the Valid function first.
|
2016-08-25 17:21:53 +03:00
|
|
|
func Get(json, path string) Result {
|
Added Subselectors
It now possible to select multiple independent paths and join
their results into a single JSON document.
For example, given the following JSON
{
"info": {
"friends": [
{"first": "Dale", "last": "Murphy", "age": 44},
{"first": "Roger", "last": "Craig", "age": 68},
{"first": "Jane", "last": "Murphy", "age": 47}
]
}
}
The path `[info.friends.0.first,info.friends.1.last]` returns
["Dale","Craig"]
Or path `{info.friends.0.first,info.friends.1.last}` returns
{"first":"Dale","last":"Craig"}
You can also rename Object members such as
`{"alt1":info.friends.0.first,"alt2":info.friends.1.last}` returns
{"alt1":"Dale","alt2":"Craig"}
Finally you can combine this with any GJSON component
`info.friends.[0.first,1.age]` returns
["Dale",68]
This feature was request by @errashe in issue #113.
2019-06-30 01:23:32 +03:00
|
|
|
if len(path) > 1 {
|
|
|
|
if !DisableModifiers {
|
|
|
|
if path[0] == '@' {
|
|
|
|
// possible modifier
|
|
|
|
var ok bool
|
2019-07-15 17:54:31 +03:00
|
|
|
var npath string
|
Added Subselectors
It now possible to select multiple independent paths and join
their results into a single JSON document.
For example, given the following JSON
{
"info": {
"friends": [
{"first": "Dale", "last": "Murphy", "age": 44},
{"first": "Roger", "last": "Craig", "age": 68},
{"first": "Jane", "last": "Murphy", "age": 47}
]
}
}
The path `[info.friends.0.first,info.friends.1.last]` returns
["Dale","Craig"]
Or path `{info.friends.0.first,info.friends.1.last}` returns
{"first":"Dale","last":"Craig"}
You can also rename Object members such as
`{"alt1":info.friends.0.first,"alt2":info.friends.1.last}` returns
{"alt1":"Dale","alt2":"Craig"}
Finally you can combine this with any GJSON component
`info.friends.[0.first,1.age]` returns
["Dale",68]
This feature was request by @errashe in issue #113.
2019-06-30 01:23:32 +03:00
|
|
|
var rjson string
|
2019-07-15 17:54:31 +03:00
|
|
|
npath, rjson, ok = execModifier(json, path)
|
Added Subselectors
It now possible to select multiple independent paths and join
their results into a single JSON document.
For example, given the following JSON
{
"info": {
"friends": [
{"first": "Dale", "last": "Murphy", "age": 44},
{"first": "Roger", "last": "Craig", "age": 68},
{"first": "Jane", "last": "Murphy", "age": 47}
]
}
}
The path `[info.friends.0.first,info.friends.1.last]` returns
["Dale","Craig"]
Or path `{info.friends.0.first,info.friends.1.last}` returns
{"first":"Dale","last":"Craig"}
You can also rename Object members such as
`{"alt1":info.friends.0.first,"alt2":info.friends.1.last}` returns
{"alt1":"Dale","alt2":"Craig"}
Finally you can combine this with any GJSON component
`info.friends.[0.first,1.age]` returns
["Dale",68]
This feature was request by @errashe in issue #113.
2019-06-30 01:23:32 +03:00
|
|
|
if ok {
|
2019-07-15 17:54:31 +03:00
|
|
|
path = npath
|
Added Subselectors
It now possible to select multiple independent paths and join
their results into a single JSON document.
For example, given the following JSON
{
"info": {
"friends": [
{"first": "Dale", "last": "Murphy", "age": 44},
{"first": "Roger", "last": "Craig", "age": 68},
{"first": "Jane", "last": "Murphy", "age": 47}
]
}
}
The path `[info.friends.0.first,info.friends.1.last]` returns
["Dale","Craig"]
Or path `{info.friends.0.first,info.friends.1.last}` returns
{"first":"Dale","last":"Craig"}
You can also rename Object members such as
`{"alt1":info.friends.0.first,"alt2":info.friends.1.last}` returns
{"alt1":"Dale","alt2":"Craig"}
Finally you can combine this with any GJSON component
`info.friends.[0.first,1.age]` returns
["Dale",68]
This feature was request by @errashe in issue #113.
2019-06-30 01:23:32 +03:00
|
|
|
if len(path) > 0 && (path[0] == '|' || path[0] == '.') {
|
|
|
|
res := Get(rjson, path[1:])
|
|
|
|
res.Index = 0
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
return Parse(rjson)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if path[0] == '[' || path[0] == '{' {
|
|
|
|
// using a subselector path
|
|
|
|
kind := path[0]
|
2019-02-17 04:29:39 +03:00
|
|
|
var ok bool
|
Added Subselectors
It now possible to select multiple independent paths and join
their results into a single JSON document.
For example, given the following JSON
{
"info": {
"friends": [
{"first": "Dale", "last": "Murphy", "age": 44},
{"first": "Roger", "last": "Craig", "age": 68},
{"first": "Jane", "last": "Murphy", "age": 47}
]
}
}
The path `[info.friends.0.first,info.friends.1.last]` returns
["Dale","Craig"]
Or path `{info.friends.0.first,info.friends.1.last}` returns
{"first":"Dale","last":"Craig"}
You can also rename Object members such as
`{"alt1":info.friends.0.first,"alt2":info.friends.1.last}` returns
{"alt1":"Dale","alt2":"Craig"}
Finally you can combine this with any GJSON component
`info.friends.[0.first,1.age]` returns
["Dale",68]
This feature was request by @errashe in issue #113.
2019-06-30 01:23:32 +03:00
|
|
|
var subs []subSelector
|
|
|
|
subs, path, ok = parseSubSelectors(path)
|
2019-02-17 04:29:39 +03:00
|
|
|
if ok {
|
Added Subselectors
It now possible to select multiple independent paths and join
their results into a single JSON document.
For example, given the following JSON
{
"info": {
"friends": [
{"first": "Dale", "last": "Murphy", "age": 44},
{"first": "Roger", "last": "Craig", "age": 68},
{"first": "Jane", "last": "Murphy", "age": 47}
]
}
}
The path `[info.friends.0.first,info.friends.1.last]` returns
["Dale","Craig"]
Or path `{info.friends.0.first,info.friends.1.last}` returns
{"first":"Dale","last":"Craig"}
You can also rename Object members such as
`{"alt1":info.friends.0.first,"alt2":info.friends.1.last}` returns
{"alt1":"Dale","alt2":"Craig"}
Finally you can combine this with any GJSON component
`info.friends.[0.first,1.age]` returns
["Dale",68]
This feature was request by @errashe in issue #113.
2019-06-30 01:23:32 +03:00
|
|
|
if len(path) == 0 || (path[0] == '|' || path[0] == '.') {
|
|
|
|
var b []byte
|
|
|
|
b = append(b, kind)
|
|
|
|
var i int
|
|
|
|
for _, sub := range subs {
|
|
|
|
res := Get(json, sub.path)
|
|
|
|
if res.Exists() {
|
|
|
|
if i > 0 {
|
|
|
|
b = append(b, ',')
|
|
|
|
}
|
|
|
|
if kind == '{' {
|
|
|
|
if len(sub.name) > 0 {
|
|
|
|
if sub.name[0] == '"' && Valid(sub.name) {
|
|
|
|
b = append(b, sub.name...)
|
|
|
|
} else {
|
|
|
|
b = appendJSONString(b, sub.name)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
last := nameOfLast(sub.path)
|
|
|
|
if isSimpleName(last) {
|
|
|
|
b = appendJSONString(b, last)
|
|
|
|
} else {
|
|
|
|
b = appendJSONString(b, "_")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
b = append(b, ':')
|
|
|
|
}
|
|
|
|
var raw string
|
|
|
|
if len(res.Raw) == 0 {
|
|
|
|
raw = res.String()
|
|
|
|
if len(raw) == 0 {
|
|
|
|
raw = "null"
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
raw = res.Raw
|
|
|
|
}
|
|
|
|
b = append(b, raw...)
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
b = append(b, kind+2)
|
|
|
|
var res Result
|
|
|
|
res.Raw = string(b)
|
|
|
|
res.Type = JSON
|
|
|
|
if len(path) > 0 {
|
|
|
|
res = res.Get(path[1:])
|
|
|
|
}
|
2019-02-17 04:29:39 +03:00
|
|
|
res.Index = 0
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
Added Subselectors
It now possible to select multiple independent paths and join
their results into a single JSON document.
For example, given the following JSON
{
"info": {
"friends": [
{"first": "Dale", "last": "Murphy", "age": 44},
{"first": "Roger", "last": "Craig", "age": 68},
{"first": "Jane", "last": "Murphy", "age": 47}
]
}
}
The path `[info.friends.0.first,info.friends.1.last]` returns
["Dale","Craig"]
Or path `{info.friends.0.first,info.friends.1.last}` returns
{"first":"Dale","last":"Craig"}
You can also rename Object members such as
`{"alt1":info.friends.0.first,"alt2":info.friends.1.last}` returns
{"alt1":"Dale","alt2":"Craig"}
Finally you can combine this with any GJSON component
`info.friends.[0.first,1.age]` returns
["Dale",68]
This feature was request by @errashe in issue #113.
2019-06-30 01:23:32 +03:00
|
|
|
|
2016-08-25 17:21:53 +03:00
|
|
|
var i int
|
2016-08-25 19:40:50 +03:00
|
|
|
var c = &parseContext{json: json}
|
Added support for JSON Lines
Added support for JSON Lines (http://jsonlines.org) using the `..` prefix.
Which when specified, treats the multi-lined document as an array.
For example:
```
{"name": "Gilbert", "age": 61}
{"name": "Alexa", "age": 34}
{"name": "May", "age": 57}
{"name": "Deloise", "age": 44}
```
```
..# >> 4
..1 >> {"name": "Alexa", "age": 34}
..3 >> {"name": "Deloise", "age": 44}
..#.name >> ["Gilbert","Alexa","May","Deloise"]
..#[name="May"].age >> 57
```
Closes #60
2018-02-10 01:41:16 +03:00
|
|
|
if len(path) >= 2 && path[0] == '.' && path[1] == '.' {
|
|
|
|
c.lines = true
|
|
|
|
parseArray(c, 0, path[2:])
|
|
|
|
} else {
|
|
|
|
for ; i < len(c.json); i++ {
|
|
|
|
if c.json[i] == '{' {
|
|
|
|
i++
|
|
|
|
parseObject(c, i, path)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if c.json[i] == '[' {
|
|
|
|
i++
|
|
|
|
parseArray(c, i, path)
|
|
|
|
break
|
|
|
|
}
|
2016-08-11 06:07:45 +03:00
|
|
|
}
|
|
|
|
}
|
2019-02-17 04:29:39 +03:00
|
|
|
if c.piped {
|
|
|
|
res := c.value.Get(c.pipe)
|
|
|
|
res.Index = 0
|
|
|
|
return res
|
|
|
|
}
|
2018-06-21 21:09:58 +03:00
|
|
|
fillIndex(json, c)
|
2016-08-25 19:40:50 +03:00
|
|
|
return c.value
|
|
|
|
}
|
|
|
|
|
2016-09-08 18:08:53 +03:00
|
|
|
// GetBytes searches json for the specified path.
|
|
|
|
// If working with bytes, this method preferred over Get(string(data), path)
|
|
|
|
func GetBytes(json []byte, path string) Result {
|
2018-06-21 21:09:58 +03:00
|
|
|
return getBytes(json, path)
|
2016-08-11 06:07:45 +03:00
|
|
|
}
|
|
|
|
|
2017-04-05 20:22:45 +03:00
|
|
|
// runeit returns the rune from the the \uXXXX
|
|
|
|
func runeit(json string) rune {
|
|
|
|
n, _ := strconv.ParseUint(json[:4], 16, 64)
|
|
|
|
return rune(n)
|
|
|
|
}
|
|
|
|
|
|
|
|
// unescape unescapes a string
|
2020-01-20 18:29:11 +03:00
|
|
|
func unescape(json string) string {
|
2017-04-05 20:22:45 +03:00
|
|
|
var str = make([]byte, 0, len(json))
|
|
|
|
for i := 0; i < len(json); i++ {
|
|
|
|
switch {
|
|
|
|
default:
|
|
|
|
str = append(str, json[i])
|
|
|
|
case json[i] < ' ':
|
|
|
|
return string(str)
|
|
|
|
case json[i] == '\\':
|
|
|
|
i++
|
|
|
|
if i >= len(json) {
|
|
|
|
return string(str)
|
|
|
|
}
|
|
|
|
switch json[i] {
|
|
|
|
default:
|
|
|
|
return string(str)
|
|
|
|
case '\\':
|
|
|
|
str = append(str, '\\')
|
|
|
|
case '/':
|
|
|
|
str = append(str, '/')
|
|
|
|
case 'b':
|
|
|
|
str = append(str, '\b')
|
|
|
|
case 'f':
|
|
|
|
str = append(str, '\f')
|
|
|
|
case 'n':
|
|
|
|
str = append(str, '\n')
|
|
|
|
case 'r':
|
|
|
|
str = append(str, '\r')
|
|
|
|
case 't':
|
|
|
|
str = append(str, '\t')
|
|
|
|
case '"':
|
|
|
|
str = append(str, '"')
|
|
|
|
case 'u':
|
|
|
|
if i+5 > len(json) {
|
|
|
|
return string(str)
|
|
|
|
}
|
|
|
|
r := runeit(json[i+1:])
|
|
|
|
i += 5
|
|
|
|
if utf16.IsSurrogate(r) {
|
|
|
|
// need another code
|
2019-06-28 04:03:36 +03:00
|
|
|
if len(json[i:]) >= 6 && json[i] == '\\' &&
|
|
|
|
json[i+1] == 'u' {
|
2017-04-05 20:22:45 +03:00
|
|
|
// we expect it to be correct so just consume it
|
|
|
|
r = utf16.DecodeRune(r, runeit(json[i+2:]))
|
|
|
|
i += 6
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// provide enough space to encode the largest utf8 possible
|
|
|
|
str = append(str, 0, 0, 0, 0, 0, 0, 0, 0)
|
|
|
|
n := utf8.EncodeRune(str[len(str)-8:], r)
|
|
|
|
str = str[:len(str)-8+n]
|
|
|
|
i-- // backtrack index by one
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return string(str)
|
2016-08-11 06:07:45 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Less return true if a token is less than another token.
|
|
|
|
// The caseSensitive paramater is used when the tokens are Strings.
|
|
|
|
// The order when comparing two different type is:
|
|
|
|
//
|
2016-08-22 16:05:51 +03:00
|
|
|
// Null < False < Number < String < True < JSON
|
2016-08-11 06:07:45 +03:00
|
|
|
//
|
|
|
|
func (t Result) Less(token Result, caseSensitive bool) bool {
|
|
|
|
if t.Type < token.Type {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if t.Type > token.Type {
|
|
|
|
return false
|
|
|
|
}
|
2016-08-21 20:10:55 +03:00
|
|
|
if t.Type == String {
|
2016-08-11 06:07:45 +03:00
|
|
|
if caseSensitive {
|
|
|
|
return t.Str < token.Str
|
|
|
|
}
|
|
|
|
return stringLessInsensitive(t.Str, token.Str)
|
2016-08-21 20:10:55 +03:00
|
|
|
}
|
|
|
|
if t.Type == Number {
|
|
|
|
return t.Num < token.Num
|
|
|
|
}
|
|
|
|
return t.Raw < token.Raw
|
2016-08-11 06:07:45 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func stringLessInsensitive(a, b string) bool {
|
|
|
|
for i := 0; i < len(a) && i < len(b); i++ {
|
|
|
|
if a[i] >= 'A' && a[i] <= 'Z' {
|
|
|
|
if b[i] >= 'A' && b[i] <= 'Z' {
|
|
|
|
// both are uppercase, do nothing
|
|
|
|
if a[i] < b[i] {
|
|
|
|
return true
|
|
|
|
} else if a[i] > b[i] {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// a is uppercase, convert a to lowercase
|
|
|
|
if a[i]+32 < b[i] {
|
|
|
|
return true
|
|
|
|
} else if a[i]+32 > b[i] {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if b[i] >= 'A' && b[i] <= 'Z' {
|
|
|
|
// b is uppercase, convert b to lowercase
|
|
|
|
if a[i] < b[i]+32 {
|
|
|
|
return true
|
|
|
|
} else if a[i] > b[i]+32 {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// neither are uppercase
|
|
|
|
if a[i] < b[i] {
|
|
|
|
return true
|
|
|
|
} else if a[i] > b[i] {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return len(a) < len(b)
|
|
|
|
}
|
2016-11-29 01:10:11 +03:00
|
|
|
|
|
|
|
// parseAny parses the next value from a json string.
|
|
|
|
// A Result is returned when the hit param is set.
|
|
|
|
// The return values are (i int, res Result, ok bool)
|
|
|
|
func parseAny(json string, i int, hit bool) (int, Result, bool) {
|
|
|
|
var res Result
|
|
|
|
var val string
|
|
|
|
for ; i < len(json); i++ {
|
|
|
|
if json[i] == '{' || json[i] == '[' {
|
|
|
|
i, val = parseSquash(json, i)
|
|
|
|
if hit {
|
|
|
|
res.Raw = val
|
|
|
|
res.Type = JSON
|
|
|
|
}
|
|
|
|
return i, res, true
|
|
|
|
}
|
|
|
|
if json[i] <= ' ' {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
switch json[i] {
|
|
|
|
case '"':
|
|
|
|
i++
|
|
|
|
var vesc bool
|
|
|
|
var ok bool
|
|
|
|
i, val, vesc, ok = parseString(json, i)
|
|
|
|
if !ok {
|
|
|
|
return i, res, false
|
|
|
|
}
|
|
|
|
if hit {
|
|
|
|
res.Type = String
|
|
|
|
res.Raw = val
|
|
|
|
if vesc {
|
|
|
|
res.Str = unescape(val[1 : len(val)-1])
|
|
|
|
} else {
|
|
|
|
res.Str = val[1 : len(val)-1]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return i, res, true
|
|
|
|
case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
|
|
|
i, val = parseNumber(json, i)
|
|
|
|
if hit {
|
|
|
|
res.Raw = val
|
|
|
|
res.Type = Number
|
|
|
|
res.Num, _ = strconv.ParseFloat(val, 64)
|
|
|
|
}
|
|
|
|
return i, res, true
|
|
|
|
case 't', 'f', 'n':
|
|
|
|
vc := json[i]
|
|
|
|
i, val = parseLiteral(json, i)
|
|
|
|
if hit {
|
|
|
|
res.Raw = val
|
|
|
|
switch vc {
|
|
|
|
case 't':
|
|
|
|
res.Type = True
|
|
|
|
case 'f':
|
|
|
|
res.Type = False
|
|
|
|
}
|
|
|
|
return i, res, true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return i, res, false
|
|
|
|
}
|
|
|
|
|
|
|
|
var ( // used for testing
|
|
|
|
testWatchForFallback bool
|
|
|
|
testLastWasFallback bool
|
|
|
|
)
|
|
|
|
|
|
|
|
// GetMany searches json for the multiple paths.
|
|
|
|
// The return value is a Result array where the number of items
|
|
|
|
// will be equal to the number of input paths.
|
2017-12-22 16:58:04 +03:00
|
|
|
func GetMany(json string, path ...string) []Result {
|
|
|
|
res := make([]Result, len(path))
|
|
|
|
for i, path := range path {
|
|
|
|
res[i] = Get(json, path)
|
2016-11-29 01:10:11 +03:00
|
|
|
}
|
2017-12-22 16:58:04 +03:00
|
|
|
return res
|
2016-11-29 01:10:11 +03:00
|
|
|
}
|
|
|
|
|
2017-12-22 16:58:04 +03:00
|
|
|
// GetManyBytes searches json for the multiple paths.
|
|
|
|
// The return value is a Result array where the number of items
|
|
|
|
// will be equal to the number of input paths.
|
|
|
|
func GetManyBytes(json []byte, path ...string) []Result {
|
2018-10-25 23:34:28 +03:00
|
|
|
res := make([]Result, len(path))
|
|
|
|
for i, path := range path {
|
|
|
|
res[i] = GetBytes(json, path)
|
|
|
|
}
|
|
|
|
return res
|
2016-11-29 01:10:11 +03:00
|
|
|
}
|
2017-05-08 04:26:54 +03:00
|
|
|
|
2017-05-09 03:33:03 +03:00
|
|
|
func validpayload(data []byte, i int) (outi int, ok bool) {
|
|
|
|
for ; i < len(data); i++ {
|
|
|
|
switch data[i] {
|
|
|
|
default:
|
|
|
|
i, ok = validany(data, i)
|
|
|
|
if !ok {
|
|
|
|
return i, false
|
|
|
|
}
|
|
|
|
for ; i < len(data); i++ {
|
|
|
|
switch data[i] {
|
|
|
|
default:
|
|
|
|
return i, false
|
|
|
|
case ' ', '\t', '\n', '\r':
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return i, true
|
|
|
|
case ' ', '\t', '\n', '\r':
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return i, false
|
|
|
|
}
|
|
|
|
func validany(data []byte, i int) (outi int, ok bool) {
|
|
|
|
for ; i < len(data); i++ {
|
|
|
|
switch data[i] {
|
|
|
|
default:
|
|
|
|
return i, false
|
|
|
|
case ' ', '\t', '\n', '\r':
|
|
|
|
continue
|
|
|
|
case '{':
|
|
|
|
return validobject(data, i+1)
|
|
|
|
case '[':
|
|
|
|
return validarray(data, i+1)
|
|
|
|
case '"':
|
|
|
|
return validstring(data, i+1)
|
|
|
|
case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
|
|
|
return validnumber(data, i+1)
|
|
|
|
case 't':
|
|
|
|
return validtrue(data, i+1)
|
|
|
|
case 'f':
|
|
|
|
return validfalse(data, i+1)
|
|
|
|
case 'n':
|
|
|
|
return validnull(data, i+1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return i, false
|
|
|
|
}
|
|
|
|
func validobject(data []byte, i int) (outi int, ok bool) {
|
|
|
|
for ; i < len(data); i++ {
|
|
|
|
switch data[i] {
|
|
|
|
default:
|
|
|
|
return i, false
|
|
|
|
case ' ', '\t', '\n', '\r':
|
|
|
|
continue
|
|
|
|
case '}':
|
|
|
|
return i + 1, true
|
|
|
|
case '"':
|
|
|
|
key:
|
|
|
|
if i, ok = validstring(data, i+1); !ok {
|
|
|
|
return i, false
|
|
|
|
}
|
|
|
|
if i, ok = validcolon(data, i); !ok {
|
|
|
|
return i, false
|
|
|
|
}
|
|
|
|
if i, ok = validany(data, i); !ok {
|
|
|
|
return i, false
|
|
|
|
}
|
|
|
|
if i, ok = validcomma(data, i, '}'); !ok {
|
|
|
|
return i, false
|
|
|
|
}
|
|
|
|
if data[i] == '}' {
|
|
|
|
return i + 1, true
|
|
|
|
}
|
2018-06-13 21:46:59 +03:00
|
|
|
i++
|
2017-05-09 03:33:03 +03:00
|
|
|
for ; i < len(data); i++ {
|
2018-06-13 21:46:59 +03:00
|
|
|
switch data[i] {
|
|
|
|
default:
|
|
|
|
return i, false
|
|
|
|
case ' ', '\t', '\n', '\r':
|
|
|
|
continue
|
|
|
|
case '"':
|
2017-05-09 03:33:03 +03:00
|
|
|
goto key
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return i, false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return i, false
|
|
|
|
}
|
|
|
|
func validcolon(data []byte, i int) (outi int, ok bool) {
|
|
|
|
for ; i < len(data); i++ {
|
|
|
|
switch data[i] {
|
|
|
|
default:
|
|
|
|
return i, false
|
|
|
|
case ' ', '\t', '\n', '\r':
|
|
|
|
continue
|
|
|
|
case ':':
|
|
|
|
return i + 1, true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return i, false
|
|
|
|
}
|
|
|
|
func validcomma(data []byte, i int, end byte) (outi int, ok bool) {
|
|
|
|
for ; i < len(data); i++ {
|
|
|
|
switch data[i] {
|
|
|
|
default:
|
|
|
|
return i, false
|
|
|
|
case ' ', '\t', '\n', '\r':
|
|
|
|
continue
|
|
|
|
case ',':
|
|
|
|
return i, true
|
|
|
|
case end:
|
|
|
|
return i, true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return i, false
|
|
|
|
}
|
|
|
|
func validarray(data []byte, i int) (outi int, ok bool) {
|
|
|
|
for ; i < len(data); i++ {
|
|
|
|
switch data[i] {
|
|
|
|
default:
|
|
|
|
for ; i < len(data); i++ {
|
|
|
|
if i, ok = validany(data, i); !ok {
|
|
|
|
return i, false
|
|
|
|
}
|
|
|
|
if i, ok = validcomma(data, i, ']'); !ok {
|
|
|
|
return i, false
|
|
|
|
}
|
|
|
|
if data[i] == ']' {
|
|
|
|
return i + 1, true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case ' ', '\t', '\n', '\r':
|
|
|
|
continue
|
|
|
|
case ']':
|
|
|
|
return i + 1, true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return i, false
|
|
|
|
}
|
|
|
|
func validstring(data []byte, i int) (outi int, ok bool) {
|
|
|
|
for ; i < len(data); i++ {
|
|
|
|
if data[i] < ' ' {
|
|
|
|
return i, false
|
|
|
|
} else if data[i] == '\\' {
|
|
|
|
i++
|
2017-05-26 05:39:18 +03:00
|
|
|
if i == len(data) {
|
|
|
|
return i, false
|
|
|
|
}
|
2017-05-09 03:33:03 +03:00
|
|
|
switch data[i] {
|
|
|
|
default:
|
|
|
|
return i, false
|
|
|
|
case '"', '\\', '/', 'b', 'f', 'n', 'r', 't':
|
|
|
|
case 'u':
|
|
|
|
for j := 0; j < 4; j++ {
|
|
|
|
i++
|
|
|
|
if i >= len(data) {
|
|
|
|
return i, false
|
|
|
|
}
|
|
|
|
if !((data[i] >= '0' && data[i] <= '9') ||
|
|
|
|
(data[i] >= 'a' && data[i] <= 'f') ||
|
|
|
|
(data[i] >= 'A' && data[i] <= 'F')) {
|
|
|
|
return i, false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if data[i] == '"' {
|
|
|
|
return i + 1, true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return i, false
|
|
|
|
}
|
|
|
|
func validnumber(data []byte, i int) (outi int, ok bool) {
|
|
|
|
i--
|
|
|
|
// sign
|
|
|
|
if data[i] == '-' {
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
// int
|
|
|
|
if i == len(data) {
|
|
|
|
return i, false
|
|
|
|
}
|
|
|
|
if data[i] == '0' {
|
|
|
|
i++
|
|
|
|
} else {
|
|
|
|
for ; i < len(data); i++ {
|
|
|
|
if data[i] >= '0' && data[i] <= '9' {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// frac
|
|
|
|
if i == len(data) {
|
|
|
|
return i, true
|
|
|
|
}
|
|
|
|
if data[i] == '.' {
|
|
|
|
i++
|
|
|
|
if i == len(data) {
|
|
|
|
return i, false
|
|
|
|
}
|
|
|
|
if data[i] < '0' || data[i] > '9' {
|
|
|
|
return i, false
|
|
|
|
}
|
|
|
|
i++
|
|
|
|
for ; i < len(data); i++ {
|
|
|
|
if data[i] >= '0' && data[i] <= '9' {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// exp
|
|
|
|
if i == len(data) {
|
|
|
|
return i, true
|
|
|
|
}
|
|
|
|
if data[i] == 'e' || data[i] == 'E' {
|
|
|
|
i++
|
|
|
|
if i == len(data) {
|
|
|
|
return i, false
|
|
|
|
}
|
|
|
|
if data[i] == '+' || data[i] == '-' {
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
if i == len(data) {
|
|
|
|
return i, false
|
|
|
|
}
|
|
|
|
if data[i] < '0' || data[i] > '9' {
|
|
|
|
return i, false
|
|
|
|
}
|
|
|
|
i++
|
|
|
|
for ; i < len(data); i++ {
|
|
|
|
if data[i] >= '0' && data[i] <= '9' {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return i, true
|
|
|
|
}
|
|
|
|
|
|
|
|
func validtrue(data []byte, i int) (outi int, ok bool) {
|
2019-06-28 04:03:36 +03:00
|
|
|
if i+3 <= len(data) && data[i] == 'r' && data[i+1] == 'u' &&
|
|
|
|
data[i+2] == 'e' {
|
2017-05-09 03:33:03 +03:00
|
|
|
return i + 3, true
|
|
|
|
}
|
|
|
|
return i, false
|
|
|
|
}
|
|
|
|
func validfalse(data []byte, i int) (outi int, ok bool) {
|
2019-06-28 04:03:36 +03:00
|
|
|
if i+4 <= len(data) && data[i] == 'a' && data[i+1] == 'l' &&
|
|
|
|
data[i+2] == 's' && data[i+3] == 'e' {
|
2017-05-09 03:33:03 +03:00
|
|
|
return i + 4, true
|
|
|
|
}
|
|
|
|
return i, false
|
|
|
|
}
|
|
|
|
func validnull(data []byte, i int) (outi int, ok bool) {
|
2019-06-28 04:03:36 +03:00
|
|
|
if i+3 <= len(data) && data[i] == 'u' && data[i+1] == 'l' &&
|
|
|
|
data[i+2] == 'l' {
|
2017-05-09 03:33:03 +03:00
|
|
|
return i + 3, true
|
|
|
|
}
|
|
|
|
return i, false
|
|
|
|
}
|
|
|
|
|
|
|
|
// Valid returns true if the input is valid json.
|
2018-02-18 20:05:23 +03:00
|
|
|
//
|
|
|
|
// if !gjson.Valid(json) {
|
|
|
|
// return errors.New("invalid json")
|
|
|
|
// }
|
|
|
|
// value := gjson.Get(json, "name.last")
|
2018-02-18 20:09:03 +03:00
|
|
|
//
|
|
|
|
func Valid(json string) bool {
|
2019-02-17 00:50:53 +03:00
|
|
|
_, ok := validpayload(stringBytes(json), 0)
|
2018-02-18 20:09:03 +03:00
|
|
|
return ok
|
|
|
|
}
|
|
|
|
|
2018-04-27 16:54:16 +03:00
|
|
|
// ValidBytes returns true if the input is valid json.
|
|
|
|
//
|
|
|
|
// if !gjson.Valid(json) {
|
|
|
|
// return errors.New("invalid json")
|
|
|
|
// }
|
|
|
|
// value := gjson.Get(json, "name.last")
|
|
|
|
//
|
2019-04-05 21:08:30 +03:00
|
|
|
// If working with bytes, this method preferred over ValidBytes(string(data))
|
2018-04-27 16:54:16 +03:00
|
|
|
//
|
|
|
|
func ValidBytes(json []byte) bool {
|
|
|
|
_, ok := validpayload(json, 0)
|
|
|
|
return ok
|
|
|
|
}
|
|
|
|
|
2017-05-26 05:39:18 +03:00
|
|
|
func parseUint(s string) (n uint64, ok bool) {
|
|
|
|
var i int
|
|
|
|
if i == len(s) {
|
|
|
|
return 0, false
|
|
|
|
}
|
|
|
|
for ; i < len(s); i++ {
|
|
|
|
if s[i] >= '0' && s[i] <= '9' {
|
|
|
|
n = n*10 + uint64(s[i]-'0')
|
|
|
|
} else {
|
|
|
|
return 0, false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return n, true
|
|
|
|
}
|
|
|
|
|
|
|
|
func parseInt(s string) (n int64, ok bool) {
|
|
|
|
var i int
|
|
|
|
var sign bool
|
|
|
|
if len(s) > 0 && s[0] == '-' {
|
|
|
|
sign = true
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
if i == len(s) {
|
|
|
|
return 0, false
|
|
|
|
}
|
|
|
|
for ; i < len(s); i++ {
|
|
|
|
if s[i] >= '0' && s[i] <= '9' {
|
|
|
|
n = n*10 + int64(s[i]-'0')
|
|
|
|
} else {
|
|
|
|
return 0, false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if sign {
|
|
|
|
return n * -1, true
|
|
|
|
}
|
|
|
|
return n, true
|
|
|
|
}
|
|
|
|
|
|
|
|
const minUint53 = 0
|
|
|
|
const maxUint53 = 4503599627370495
|
|
|
|
const minInt53 = -2251799813685248
|
|
|
|
const maxInt53 = 2251799813685247
|
|
|
|
|
|
|
|
func floatToUint(f float64) (n uint64, ok bool) {
|
|
|
|
n = uint64(f)
|
|
|
|
if float64(n) == f && n >= minUint53 && n <= maxUint53 {
|
|
|
|
return n, true
|
|
|
|
}
|
|
|
|
return 0, false
|
|
|
|
}
|
|
|
|
|
|
|
|
func floatToInt(f float64) (n int64, ok bool) {
|
|
|
|
n = int64(f)
|
|
|
|
if float64(n) == f && n >= minInt53 && n <= maxInt53 {
|
|
|
|
return n, true
|
|
|
|
}
|
|
|
|
return 0, false
|
|
|
|
}
|
2019-02-17 04:29:39 +03:00
|
|
|
|
|
|
|
// execModifier parses the path to find a matching modifier function.
|
|
|
|
// then input expects that the path already starts with a '@'
|
|
|
|
func execModifier(json, path string) (pathOut, res string, ok bool) {
|
|
|
|
name := path[1:]
|
|
|
|
var hasArgs bool
|
|
|
|
for i := 1; i < len(path); i++ {
|
|
|
|
if path[i] == ':' {
|
|
|
|
pathOut = path[i+1:]
|
|
|
|
name = path[1:i]
|
|
|
|
hasArgs = len(pathOut) > 0
|
|
|
|
break
|
|
|
|
}
|
2019-06-29 22:10:41 +03:00
|
|
|
if path[i] == '|' {
|
|
|
|
pathOut = path[i:]
|
|
|
|
name = path[1:i]
|
|
|
|
break
|
2019-02-17 04:29:39 +03:00
|
|
|
}
|
2019-06-28 04:27:53 +03:00
|
|
|
if path[i] == '.' {
|
|
|
|
pathOut = path[i:]
|
|
|
|
name = path[1:i]
|
|
|
|
break
|
|
|
|
}
|
2019-02-17 04:29:39 +03:00
|
|
|
}
|
|
|
|
if fn, ok := modifiers[name]; ok {
|
|
|
|
var args string
|
|
|
|
if hasArgs {
|
|
|
|
var parsedArgs bool
|
|
|
|
switch pathOut[0] {
|
|
|
|
case '{', '[', '"':
|
|
|
|
res := Parse(pathOut)
|
|
|
|
if res.Exists() {
|
2019-11-18 19:51:37 +03:00
|
|
|
args = squash(pathOut)
|
2019-02-17 04:29:39 +03:00
|
|
|
pathOut = pathOut[len(args):]
|
|
|
|
parsedArgs = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !parsedArgs {
|
2019-06-29 22:10:41 +03:00
|
|
|
idx := strings.IndexByte(pathOut, '|')
|
2019-02-17 04:29:39 +03:00
|
|
|
if idx == -1 {
|
|
|
|
args = pathOut
|
|
|
|
pathOut = ""
|
|
|
|
} else {
|
|
|
|
args = pathOut[:idx]
|
|
|
|
pathOut = pathOut[idx:]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return pathOut, fn(json, args), true
|
|
|
|
}
|
|
|
|
return pathOut, res, false
|
|
|
|
}
|
|
|
|
|
Added new modifiers
`@flatten` Flattens an array with child arrays.
[1,[2],[3,4],[5,[6,7]]] -> [1,2,3,4,5,[6,7]]
The {"deep":true} arg can be provide for deep flattening.
[1,[2],[3,4],[5,[6,7]]] -> [1,2,3,4,5,6,7]
The original json is returned when the json is not an array.
`@join` Joins multiple objects into a single object.
[{"first":"Tom"},{"last":"Smith"}] -> {"first","Tom","last":"Smith"}
The arg can be "true" to specify that duplicate keys should be preserved.
[{"first":"Tom","age":37},{"age":41}] -> {"first","Tom","age":37,"age":41}
Without preserved keys:
[{"first":"Tom","age":37},{"age":41}] -> {"first","Tom","age":41}
The original json is returned when the json is not an object.
`@valid` Ensures that the json is valid before moving on. An
empty string is returned when the json is not valid, otherwise
it returns the original json.
2020-02-10 21:13:30 +03:00
|
|
|
// unwrap removes the '[]' or '{}' characters around json
|
|
|
|
func unwrap(json string) string {
|
|
|
|
json = trim(json)
|
|
|
|
if len(json) >= 2 && json[0] == '[' || json[0] == '{' {
|
|
|
|
json = json[1 : len(json)-1]
|
|
|
|
}
|
|
|
|
return json
|
|
|
|
}
|
|
|
|
|
2019-02-17 04:29:39 +03:00
|
|
|
// DisableModifiers will disable the modifier syntax
|
|
|
|
var DisableModifiers = false
|
|
|
|
|
|
|
|
var modifiers = map[string]func(json, arg string) string{
|
|
|
|
"pretty": modPretty,
|
|
|
|
"ugly": modUgly,
|
|
|
|
"reverse": modReverse,
|
2020-01-13 13:51:52 +03:00
|
|
|
"this": modThis,
|
Added new modifiers
`@flatten` Flattens an array with child arrays.
[1,[2],[3,4],[5,[6,7]]] -> [1,2,3,4,5,[6,7]]
The {"deep":true} arg can be provide for deep flattening.
[1,[2],[3,4],[5,[6,7]]] -> [1,2,3,4,5,6,7]
The original json is returned when the json is not an array.
`@join` Joins multiple objects into a single object.
[{"first":"Tom"},{"last":"Smith"}] -> {"first","Tom","last":"Smith"}
The arg can be "true" to specify that duplicate keys should be preserved.
[{"first":"Tom","age":37},{"age":41}] -> {"first","Tom","age":37,"age":41}
Without preserved keys:
[{"first":"Tom","age":37},{"age":41}] -> {"first","Tom","age":41}
The original json is returned when the json is not an object.
`@valid` Ensures that the json is valid before moving on. An
empty string is returned when the json is not valid, otherwise
it returns the original json.
2020-02-10 21:13:30 +03:00
|
|
|
"flatten": modFlatten,
|
|
|
|
"join": modJoin,
|
|
|
|
"valid": modValid,
|
2019-02-17 04:29:39 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// AddModifier binds a custom modifier command to the GJSON syntax.
|
|
|
|
// This operation is not thread safe and should be executed prior to
|
|
|
|
// using all other gjson function.
|
|
|
|
func AddModifier(name string, fn func(json, arg string) string) {
|
|
|
|
modifiers[name] = fn
|
|
|
|
}
|
|
|
|
|
|
|
|
// ModifierExists returns true when the specified modifier exists.
|
|
|
|
func ModifierExists(name string, fn func(json, arg string) string) bool {
|
|
|
|
_, ok := modifiers[name]
|
|
|
|
return ok
|
|
|
|
}
|
|
|
|
|
|
|
|
// @pretty modifier makes the json look nice.
|
|
|
|
func modPretty(json, arg string) string {
|
|
|
|
if len(arg) > 0 {
|
|
|
|
opts := *pretty.DefaultOptions
|
|
|
|
Parse(arg).ForEach(func(key, value Result) bool {
|
|
|
|
switch key.String() {
|
|
|
|
case "sortKeys":
|
|
|
|
opts.SortKeys = value.Bool()
|
|
|
|
case "indent":
|
|
|
|
opts.Indent = value.String()
|
|
|
|
case "prefix":
|
|
|
|
opts.Prefix = value.String()
|
|
|
|
case "width":
|
|
|
|
opts.Width = int(value.Int())
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
})
|
|
|
|
return bytesString(pretty.PrettyOptions(stringBytes(json), &opts))
|
|
|
|
}
|
|
|
|
return bytesString(pretty.Pretty(stringBytes(json)))
|
|
|
|
}
|
|
|
|
|
2020-01-13 13:51:52 +03:00
|
|
|
// @this returns the current element. Can be used to retrieve the root element.
|
|
|
|
func modThis(json, arg string) string {
|
|
|
|
return json
|
|
|
|
}
|
|
|
|
|
2019-02-17 04:29:39 +03:00
|
|
|
// @ugly modifier removes all whitespace.
|
|
|
|
func modUgly(json, arg string) string {
|
|
|
|
return bytesString(pretty.Ugly(stringBytes(json)))
|
|
|
|
}
|
|
|
|
|
|
|
|
// @reverse reverses array elements or root object members.
|
|
|
|
func modReverse(json, arg string) string {
|
|
|
|
res := Parse(json)
|
|
|
|
if res.IsArray() {
|
|
|
|
var values []Result
|
|
|
|
res.ForEach(func(_, value Result) bool {
|
|
|
|
values = append(values, value)
|
|
|
|
return true
|
|
|
|
})
|
|
|
|
out := make([]byte, 0, len(json))
|
|
|
|
out = append(out, '[')
|
|
|
|
for i, j := len(values)-1, 0; i >= 0; i, j = i-1, j+1 {
|
|
|
|
if j > 0 {
|
|
|
|
out = append(out, ',')
|
|
|
|
}
|
|
|
|
out = append(out, values[i].Raw...)
|
|
|
|
}
|
|
|
|
out = append(out, ']')
|
|
|
|
return bytesString(out)
|
|
|
|
}
|
|
|
|
if res.IsObject() {
|
|
|
|
var keyValues []Result
|
|
|
|
res.ForEach(func(key, value Result) bool {
|
|
|
|
keyValues = append(keyValues, key, value)
|
|
|
|
return true
|
|
|
|
})
|
|
|
|
out := make([]byte, 0, len(json))
|
|
|
|
out = append(out, '{')
|
|
|
|
for i, j := len(keyValues)-2, 0; i >= 0; i, j = i-2, j+1 {
|
|
|
|
if j > 0 {
|
|
|
|
out = append(out, ',')
|
|
|
|
}
|
|
|
|
out = append(out, keyValues[i+0].Raw...)
|
|
|
|
out = append(out, ':')
|
|
|
|
out = append(out, keyValues[i+1].Raw...)
|
|
|
|
}
|
|
|
|
out = append(out, '}')
|
|
|
|
return bytesString(out)
|
|
|
|
}
|
|
|
|
return json
|
|
|
|
}
|
Added new modifiers
`@flatten` Flattens an array with child arrays.
[1,[2],[3,4],[5,[6,7]]] -> [1,2,3,4,5,[6,7]]
The {"deep":true} arg can be provide for deep flattening.
[1,[2],[3,4],[5,[6,7]]] -> [1,2,3,4,5,6,7]
The original json is returned when the json is not an array.
`@join` Joins multiple objects into a single object.
[{"first":"Tom"},{"last":"Smith"}] -> {"first","Tom","last":"Smith"}
The arg can be "true" to specify that duplicate keys should be preserved.
[{"first":"Tom","age":37},{"age":41}] -> {"first","Tom","age":37,"age":41}
Without preserved keys:
[{"first":"Tom","age":37},{"age":41}] -> {"first","Tom","age":41}
The original json is returned when the json is not an object.
`@valid` Ensures that the json is valid before moving on. An
empty string is returned when the json is not valid, otherwise
it returns the original json.
2020-02-10 21:13:30 +03:00
|
|
|
|
|
|
|
// @flatten an array with child arrays.
|
|
|
|
// [1,[2],[3,4],[5,[6,7]]] -> [1,2,3,4,5,[6,7]]
|
|
|
|
// The {"deep":true} arg can be provide for deep flattening.
|
|
|
|
// [1,[2],[3,4],[5,[6,7]]] -> [1,2,3,4,5,6,7]
|
|
|
|
// The original json is returned when the json is not an array.
|
|
|
|
func modFlatten(json, arg string) string {
|
|
|
|
res := Parse(json)
|
|
|
|
if !res.IsArray() {
|
|
|
|
return json
|
|
|
|
}
|
|
|
|
var deep bool
|
|
|
|
if arg != "" {
|
|
|
|
Parse(arg).ForEach(func(key, value Result) bool {
|
|
|
|
if key.String() == "deep" {
|
|
|
|
deep = value.Bool()
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
})
|
|
|
|
}
|
|
|
|
var out []byte
|
|
|
|
out = append(out, '[')
|
|
|
|
var idx int
|
|
|
|
res.ForEach(func(_, value Result) bool {
|
|
|
|
if idx > 0 {
|
|
|
|
out = append(out, ',')
|
|
|
|
}
|
|
|
|
if value.IsArray() {
|
|
|
|
if deep {
|
|
|
|
out = append(out, unwrap(modFlatten(value.Raw, arg))...)
|
|
|
|
} else {
|
|
|
|
out = append(out, unwrap(value.Raw)...)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
out = append(out, value.Raw...)
|
|
|
|
}
|
|
|
|
idx++
|
|
|
|
return true
|
|
|
|
})
|
|
|
|
out = append(out, ']')
|
|
|
|
return bytesString(out)
|
|
|
|
}
|
|
|
|
|
|
|
|
// @join multiple objects into a single object.
|
|
|
|
// [{"first":"Tom"},{"last":"Smith"}] -> {"first","Tom","last":"Smith"}
|
|
|
|
// The arg can be "true" to specify that duplicate keys should be preserved.
|
|
|
|
// [{"first":"Tom","age":37},{"age":41}] -> {"first","Tom","age":37,"age":41}
|
|
|
|
// Without preserved keys:
|
|
|
|
// [{"first":"Tom","age":37},{"age":41}] -> {"first","Tom","age":41}
|
|
|
|
// The original json is returned when the json is not an object.
|
|
|
|
func modJoin(json, arg string) string {
|
|
|
|
res := Parse(json)
|
|
|
|
if !res.IsArray() {
|
|
|
|
return json
|
|
|
|
}
|
|
|
|
var preserve bool
|
|
|
|
if arg != "" {
|
|
|
|
Parse(arg).ForEach(func(key, value Result) bool {
|
|
|
|
if key.String() == "preserve" {
|
|
|
|
preserve = value.Bool()
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
})
|
|
|
|
}
|
|
|
|
var out []byte
|
|
|
|
out = append(out, '{')
|
|
|
|
if preserve {
|
|
|
|
// Preserve duplicate keys.
|
|
|
|
var idx int
|
|
|
|
res.ForEach(func(_, value Result) bool {
|
|
|
|
if !value.IsObject() {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if idx > 0 {
|
|
|
|
out = append(out, ',')
|
|
|
|
}
|
|
|
|
out = append(out, unwrap(value.Raw)...)
|
|
|
|
idx++
|
|
|
|
return true
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
// Deduplicate keys and generate an object with stable ordering.
|
|
|
|
var keys []Result
|
|
|
|
kvals := make(map[string]Result)
|
|
|
|
res.ForEach(func(_, value Result) bool {
|
|
|
|
if !value.IsObject() {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
value.ForEach(func(key, value Result) bool {
|
|
|
|
k := key.String()
|
|
|
|
if _, ok := kvals[k]; !ok {
|
|
|
|
keys = append(keys, key)
|
|
|
|
}
|
|
|
|
kvals[k] = value
|
|
|
|
return true
|
|
|
|
})
|
|
|
|
return true
|
|
|
|
})
|
|
|
|
for i := 0; i < len(keys); i++ {
|
|
|
|
if i > 0 {
|
|
|
|
out = append(out, ',')
|
|
|
|
}
|
|
|
|
out = append(out, keys[i].Raw...)
|
|
|
|
out = append(out, ':')
|
|
|
|
out = append(out, kvals[keys[i].String()].Raw...)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
out = append(out, '}')
|
|
|
|
return bytesString(out)
|
|
|
|
}
|
|
|
|
|
|
|
|
// @valid ensures that the json is valid before moving on. An empty string is
|
|
|
|
// returned when the json is not valid, otherwise it returns the original json.
|
|
|
|
func modValid(json, arg string) string {
|
|
|
|
if !Valid(json) {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return json
|
|
|
|
}
|
2020-02-28 19:37:46 +03:00
|
|
|
|
|
|
|
// getBytes casts the input json bytes to a string and safely returns the
|
|
|
|
// results as uniquely allocated data. This operation is intended to minimize
|
|
|
|
// copies and allocations for the large json string->[]byte.
|
|
|
|
func getBytes(json []byte, path string) Result {
|
|
|
|
var result Result
|
|
|
|
if json != nil {
|
|
|
|
// unsafe cast to string
|
|
|
|
result = Get(*(*string)(unsafe.Pointer(&json)), path)
|
|
|
|
// safely get the string headers
|
|
|
|
rawhi := *(*reflect.StringHeader)(unsafe.Pointer(&result.Raw))
|
|
|
|
strhi := *(*reflect.StringHeader)(unsafe.Pointer(&result.Str))
|
|
|
|
// create byte slice headers
|
|
|
|
rawh := reflect.SliceHeader{Data: rawhi.Data, Len: rawhi.Len}
|
|
|
|
strh := reflect.SliceHeader{Data: strhi.Data, Len: strhi.Len}
|
|
|
|
if strh.Data == 0 {
|
|
|
|
// str is nil
|
|
|
|
if rawh.Data == 0 {
|
|
|
|
// raw is nil
|
|
|
|
result.Raw = ""
|
|
|
|
} else {
|
|
|
|
// raw has data, safely copy the slice header to a string
|
|
|
|
result.Raw = string(*(*[]byte)(unsafe.Pointer(&rawh)))
|
|
|
|
}
|
|
|
|
result.Str = ""
|
|
|
|
} else if rawh.Data == 0 {
|
|
|
|
// raw is nil
|
|
|
|
result.Raw = ""
|
|
|
|
// str has data, safely copy the slice header to a string
|
|
|
|
result.Str = string(*(*[]byte)(unsafe.Pointer(&strh)))
|
|
|
|
} else if strh.Data >= rawh.Data &&
|
|
|
|
int(strh.Data)+strh.Len <= int(rawh.Data)+rawh.Len {
|
|
|
|
// Str is a substring of Raw.
|
|
|
|
start := int(strh.Data - rawh.Data)
|
|
|
|
// safely copy the raw slice header
|
|
|
|
result.Raw = string(*(*[]byte)(unsafe.Pointer(&rawh)))
|
|
|
|
// substring the raw
|
|
|
|
result.Str = result.Raw[start : start+strh.Len]
|
|
|
|
} else {
|
|
|
|
// safely copy both the raw and str slice headers to strings
|
|
|
|
result.Raw = string(*(*[]byte)(unsafe.Pointer(&rawh)))
|
|
|
|
result.Str = string(*(*[]byte)(unsafe.Pointer(&strh)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
// fillIndex finds the position of Raw data and assigns it to the Index field
|
|
|
|
// of the resulting value. If the position cannot be found then Index zero is
|
|
|
|
// used instead.
|
|
|
|
func fillIndex(json string, c *parseContext) {
|
|
|
|
if len(c.value.Raw) > 0 && !c.calcd {
|
|
|
|
jhdr := *(*reflect.StringHeader)(unsafe.Pointer(&json))
|
|
|
|
rhdr := *(*reflect.StringHeader)(unsafe.Pointer(&(c.value.Raw)))
|
|
|
|
c.value.Index = int(rhdr.Data - jhdr.Data)
|
|
|
|
if c.value.Index < 0 || c.value.Index >= len(json) {
|
|
|
|
c.value.Index = 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func stringBytes(s string) []byte {
|
|
|
|
return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
|
|
|
|
Data: (*reflect.StringHeader)(unsafe.Pointer(&s)).Data,
|
|
|
|
Len: len(s),
|
|
|
|
Cap: len(s),
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
|
|
|
func bytesString(b []byte) string {
|
|
|
|
return *(*string)(unsafe.Pointer(&b))
|
|
|
|
}
|