mirror of https://github.com/tidwall/gjson.git
Added new tilde types and fixed ~false
This commit fixes an issue with ~false where the it's value was simply the opposite of ~true. Now ~false explicitly checks for false-ish values. Also added ~null and ~* for testing null-ish and non-existent values. see #327
This commit is contained in:
parent
e14b8d3708
commit
8d2c36ffa4
23
SYNTAX.md
23
SYNTAX.md
|
@ -137,12 +137,21 @@ next major release.*
|
||||||
|
|
||||||
The `~` (tilde) operator will convert a value to a boolean before comparison.
|
The `~` (tilde) operator will convert a value to a boolean before comparison.
|
||||||
|
|
||||||
|
Supported tilde comparison type are:
|
||||||
|
|
||||||
|
```
|
||||||
|
~true Converts true-ish values to true
|
||||||
|
~false Converts false-ish and non-existent values to true
|
||||||
|
~null Converts null and non-existent values to true
|
||||||
|
~* Converts any existing value to true
|
||||||
|
```
|
||||||
|
|
||||||
For example, using the following JSON:
|
For example, using the following JSON:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"vals": [
|
"vals": [
|
||||||
{ "a": 1, "b": true },
|
{ "a": 1, "b": "data" },
|
||||||
{ "a": 2, "b": true },
|
{ "a": 2, "b": true },
|
||||||
{ "a": 3, "b": false },
|
{ "a": 3, "b": false },
|
||||||
{ "a": 4, "b": "0" },
|
{ "a": 4, "b": "0" },
|
||||||
|
@ -157,15 +166,23 @@ For example, using the following JSON:
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
You can now query for all true(ish) or false(ish) values:
|
To query for all true-ish or false-ish values:
|
||||||
|
|
||||||
```
|
```
|
||||||
vals.#(b==~true)#.a >> [1,2,6,7,8]
|
vals.#(b==~true)#.a >> [2,6,7,8]
|
||||||
vals.#(b==~false)#.a >> [3,4,5,9,10,11]
|
vals.#(b==~false)#.a >> [3,4,5,9,10,11]
|
||||||
```
|
```
|
||||||
|
|
||||||
The last value which was non-existent is treated as `false`
|
The last value which was non-existent is treated as `false`
|
||||||
|
|
||||||
|
To query for null and explicit value existence:
|
||||||
|
|
||||||
|
```
|
||||||
|
vals.#(b==~null)#.a >> [10,11]
|
||||||
|
vals.#(b==~*)#.a >> [1,2,3,4,5,6,7,8,9,10]
|
||||||
|
vals.#(b!=~*)#.a >> [11]
|
||||||
|
```
|
||||||
|
|
||||||
### Dot vs Pipe
|
### Dot vs Pipe
|
||||||
|
|
||||||
The `.` is standard separator, but it's also possible to use a `|`.
|
The `.` is standard separator, but it's also possible to use a `|`.
|
||||||
|
|
90
gjson.go
90
gjson.go
|
@ -661,7 +661,6 @@ func (t Result) Exists() bool {
|
||||||
// nil, for JSON null
|
// nil, for JSON null
|
||||||
// map[string]interface{}, for JSON objects
|
// map[string]interface{}, for JSON objects
|
||||||
// []interface{}, for JSON arrays
|
// []interface{}, for JSON arrays
|
||||||
//
|
|
||||||
func (t Result) Value() interface{} {
|
func (t Result) Value() interface{} {
|
||||||
if t.Type == String {
|
if t.Type == String {
|
||||||
return t.Str
|
return t.Str
|
||||||
|
@ -826,16 +825,25 @@ func parseArrayPath(path string) (r arrayPathResult) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// splitQuery takes a query and splits it into three parts:
|
// splitQuery takes a query and splits it into three parts:
|
||||||
|
//
|
||||||
// path, op, middle, and right.
|
// path, op, middle, and right.
|
||||||
|
//
|
||||||
// So for this query:
|
// So for this query:
|
||||||
|
//
|
||||||
// #(first_name=="Murphy").last
|
// #(first_name=="Murphy").last
|
||||||
|
//
|
||||||
// Becomes
|
// Becomes
|
||||||
|
//
|
||||||
// first_name # path
|
// first_name # path
|
||||||
// =="Murphy" # middle
|
// =="Murphy" # middle
|
||||||
// .last # right
|
// .last # right
|
||||||
|
//
|
||||||
// Or,
|
// Or,
|
||||||
|
//
|
||||||
// #(service_roles.#(=="one")).cap
|
// #(service_roles.#(=="one")).cap
|
||||||
|
//
|
||||||
// Becomes
|
// Becomes
|
||||||
|
//
|
||||||
// service_roles.#(=="one") # path
|
// service_roles.#(=="one") # path
|
||||||
// # middle
|
// # middle
|
||||||
// .cap # right
|
// .cap # right
|
||||||
|
@ -1251,16 +1259,75 @@ func matchLimit(str, pattern string) bool {
|
||||||
return matched
|
return matched
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func falseish(t Result) bool {
|
||||||
|
switch t.Type {
|
||||||
|
case Null:
|
||||||
|
return true
|
||||||
|
case False:
|
||||||
|
return true
|
||||||
|
case String:
|
||||||
|
b, err := strconv.ParseBool(strings.ToLower(t.Str))
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return !b
|
||||||
|
case Number:
|
||||||
|
return t.Num == 0
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func trueish(t Result) bool {
|
||||||
|
switch t.Type {
|
||||||
|
case True:
|
||||||
|
return true
|
||||||
|
case String:
|
||||||
|
b, err := strconv.ParseBool(strings.ToLower(t.Str))
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
case Number:
|
||||||
|
return t.Num != 0
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func nullish(t Result) bool {
|
||||||
|
return t.Type == Null
|
||||||
|
}
|
||||||
|
|
||||||
func queryMatches(rp *arrayPathResult, value Result) bool {
|
func queryMatches(rp *arrayPathResult, value Result) bool {
|
||||||
rpv := rp.query.value
|
rpv := rp.query.value
|
||||||
if len(rpv) > 0 && rpv[0] == '~' {
|
if len(rpv) > 0 {
|
||||||
|
if rpv[0] == '~' {
|
||||||
// convert to bool
|
// convert to bool
|
||||||
rpv = rpv[1:]
|
rpv = rpv[1:]
|
||||||
if value.Bool() {
|
var ish, ok bool
|
||||||
|
switch rpv {
|
||||||
|
case "*":
|
||||||
|
ish, ok = value.Exists(), true
|
||||||
|
case "null":
|
||||||
|
ish, ok = nullish(value), true
|
||||||
|
case "true":
|
||||||
|
ish, ok = trueish(value), true
|
||||||
|
case "false":
|
||||||
|
ish, ok = falseish(value), true
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
rpv = "true"
|
||||||
|
if ish {
|
||||||
value = Result{Type: True}
|
value = Result{Type: True}
|
||||||
} else {
|
} else {
|
||||||
value = Result{Type: False}
|
value = Result{Type: False}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
rpv = ""
|
||||||
|
value = Result{}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !value.Exists() {
|
if !value.Exists() {
|
||||||
return false
|
return false
|
||||||
|
@ -2127,7 +2194,6 @@ func unescape(json string) string {
|
||||||
// The order when comparing two different type is:
|
// The order when comparing two different type is:
|
||||||
//
|
//
|
||||||
// Null < False < Number < String < True < JSON
|
// Null < False < Number < String < True < JSON
|
||||||
//
|
|
||||||
func (t Result) Less(token Result, caseSensitive bool) bool {
|
func (t Result) Less(token Result, caseSensitive bool) bool {
|
||||||
if t.Type < token.Type {
|
if t.Type < token.Type {
|
||||||
return true
|
return true
|
||||||
|
@ -2560,7 +2626,6 @@ func validnull(data []byte, i int) (outi int, ok bool) {
|
||||||
// return errors.New("invalid json")
|
// return errors.New("invalid json")
|
||||||
// }
|
// }
|
||||||
// value := gjson.Get(json, "name.last")
|
// value := gjson.Get(json, "name.last")
|
||||||
//
|
|
||||||
func Valid(json string) bool {
|
func Valid(json string) bool {
|
||||||
_, ok := validpayload(stringBytes(json), 0)
|
_, ok := validpayload(stringBytes(json), 0)
|
||||||
return ok
|
return ok
|
||||||
|
@ -2574,7 +2639,6 @@ func Valid(json string) bool {
|
||||||
// value := gjson.Get(json, "name.last")
|
// value := gjson.Get(json, "name.last")
|
||||||
//
|
//
|
||||||
// If working with bytes, this method preferred over ValidBytes(string(data))
|
// If working with bytes, this method preferred over ValidBytes(string(data))
|
||||||
//
|
|
||||||
func ValidBytes(json []byte) bool {
|
func ValidBytes(json []byte) bool {
|
||||||
_, ok := validpayload(json, 0)
|
_, ok := validpayload(json, 0)
|
||||||
return ok
|
return ok
|
||||||
|
@ -2848,9 +2912,13 @@ func modReverse(json, arg string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// @flatten an array with child arrays.
|
// @flatten an array with child arrays.
|
||||||
|
//
|
||||||
// [1,[2],[3,4],[5,[6,7]]] -> [1,2,3,4,5,[6,7]]
|
// [1,[2],[3,4],[5,[6,7]]] -> [1,2,3,4,5,[6,7]]
|
||||||
|
//
|
||||||
// The {"deep":true} arg can be provide for deep flattening.
|
// The {"deep":true} arg can be provide for deep flattening.
|
||||||
|
//
|
||||||
// [1,[2],[3,4],[5,[6,7]]] -> [1,2,3,4,5,6,7]
|
// [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.
|
// The original json is returned when the json is not an array.
|
||||||
func modFlatten(json, arg string) string {
|
func modFlatten(json, arg string) string {
|
||||||
res := Parse(json)
|
res := Parse(json)
|
||||||
|
@ -2895,6 +2963,7 @@ func modFlatten(json, arg string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// @keys extracts the keys from an object.
|
// @keys extracts the keys from an object.
|
||||||
|
//
|
||||||
// {"first":"Tom","last":"Smith"} -> ["first","last"]
|
// {"first":"Tom","last":"Smith"} -> ["first","last"]
|
||||||
func modKeys(json, arg string) string {
|
func modKeys(json, arg string) string {
|
||||||
v := Parse(json)
|
v := Parse(json)
|
||||||
|
@ -2922,6 +2991,7 @@ func modKeys(json, arg string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// @values extracts the values from an object.
|
// @values extracts the values from an object.
|
||||||
|
//
|
||||||
// {"first":"Tom","last":"Smith"} -> ["Tom","Smith"]
|
// {"first":"Tom","last":"Smith"} -> ["Tom","Smith"]
|
||||||
func modValues(json, arg string) string {
|
func modValues(json, arg string) string {
|
||||||
v := Parse(json)
|
v := Parse(json)
|
||||||
|
@ -2947,11 +3017,17 @@ func modValues(json, arg string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// @join multiple objects into a single object.
|
// @join multiple objects into a single object.
|
||||||
|
//
|
||||||
// [{"first":"Tom"},{"last":"Smith"}] -> {"first","Tom","last":"Smith"}
|
// [{"first":"Tom"},{"last":"Smith"}] -> {"first","Tom","last":"Smith"}
|
||||||
|
//
|
||||||
// The arg can be "true" to specify that duplicate keys should be preserved.
|
// 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}
|
// [{"first":"Tom","age":37},{"age":41}] -> {"first","Tom","age":37,"age":41}
|
||||||
|
//
|
||||||
// Without preserved keys:
|
// Without preserved keys:
|
||||||
|
//
|
||||||
// [{"first":"Tom","age":37},{"age":41}] -> {"first","Tom","age":41}
|
// [{"first":"Tom","age":37},{"age":41}] -> {"first","Tom","age":41}
|
||||||
|
//
|
||||||
// The original json is returned when the json is not an object.
|
// The original json is returned when the json is not an object.
|
||||||
func modJoin(json, arg string) string {
|
func modJoin(json, arg string) string {
|
||||||
res := Parse(json)
|
res := Parse(json)
|
||||||
|
@ -3024,6 +3100,7 @@ func modValid(json, arg string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// @fromstr converts a string to json
|
// @fromstr converts a string to json
|
||||||
|
//
|
||||||
// "{\"id\":1023,\"name\":\"alert\"}" -> {"id":1023,"name":"alert"}
|
// "{\"id\":1023,\"name\":\"alert\"}" -> {"id":1023,"name":"alert"}
|
||||||
func modFromStr(json, arg string) string {
|
func modFromStr(json, arg string) string {
|
||||||
if !Valid(json) {
|
if !Valid(json) {
|
||||||
|
@ -3033,6 +3110,7 @@ func modFromStr(json, arg string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// @tostr converts a string to json
|
// @tostr converts a string to json
|
||||||
|
//
|
||||||
// {"id":1023,"name":"alert"} -> "{\"id\":1023,\"name\":\"alert\"}"
|
// {"id":1023,"name":"alert"} -> "{\"id\":1023,\"name\":\"alert\"}"
|
||||||
func modToStr(str, arg string) string {
|
func modToStr(str, arg string) string {
|
||||||
return string(AppendJSONString(nil, str))
|
return string(AppendJSONString(nil, str))
|
||||||
|
|
|
@ -2129,10 +2129,10 @@ func TestEncodedQueryString(t *testing.T) {
|
||||||
assert(t, Get(json, `friends.#(last=="Murphy").age`).Int() == 47)
|
assert(t, Get(json, `friends.#(last=="Murphy").age`).Int() == 47)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBoolConvertQuery(t *testing.T) {
|
func TestTildeQueries(t *testing.T) {
|
||||||
json := `{
|
json := `{
|
||||||
"vals": [
|
"vals": [
|
||||||
{ "a": 1, "b": true },
|
{ "a": 1, "b": "data" },
|
||||||
{ "a": 2, "b": true },
|
{ "a": 2, "b": true },
|
||||||
{ "a": 3, "b": false },
|
{ "a": 3, "b": false },
|
||||||
{ "a": 4, "b": "0" },
|
{ "a": 4, "b": "0" },
|
||||||
|
@ -2146,9 +2146,54 @@ func TestBoolConvertQuery(t *testing.T) {
|
||||||
]
|
]
|
||||||
}`
|
}`
|
||||||
trues := Get(json, `vals.#(b==~true)#.a`).Raw
|
trues := Get(json, `vals.#(b==~true)#.a`).Raw
|
||||||
|
truesNOT := Get(json, `vals.#(b!=~true)#.a`).Raw
|
||||||
falses := Get(json, `vals.#(b==~false)#.a`).Raw
|
falses := Get(json, `vals.#(b==~false)#.a`).Raw
|
||||||
assert(t, trues == "[1,2,6,7,8]")
|
falsesNOT := Get(json, `vals.#(b!=~false)#.a`).Raw
|
||||||
|
nulls := Get(json, `vals.#(b==~null)#.a`).Raw
|
||||||
|
nullsNOT := Get(json, `vals.#(b!=~null)#.a`).Raw
|
||||||
|
exists := Get(json, `vals.#(b==~*)#.a`).Raw
|
||||||
|
existsNOT := Get(json, `vals.#(b!=~*)#.a`).Raw
|
||||||
|
|
||||||
|
assert(t, trues == "[2,6,7,8]")
|
||||||
|
assert(t, truesNOT == "[1,3,4,5,9,10,11]")
|
||||||
assert(t, falses == "[3,4,5,9,10,11]")
|
assert(t, falses == "[3,4,5,9,10,11]")
|
||||||
|
assert(t, falsesNOT == "[1,2,6,7,8]")
|
||||||
|
assert(t, nulls == "[10,11]")
|
||||||
|
assert(t, nullsNOT == "[1,2,3,4,5,6,7,8,9]")
|
||||||
|
assert(t, exists == "[1,2,3,4,5,6,7,8,9,10]")
|
||||||
|
assert(t, existsNOT == "[11]")
|
||||||
|
json = `{
|
||||||
|
"vals": [
|
||||||
|
{ "a": 1, "b": "something" },
|
||||||
|
{ "a": 2, "b": "else" },
|
||||||
|
{ "a": 3, "b": false },
|
||||||
|
{ "a": 4, "b": "0" },
|
||||||
|
{ "a": 5, "b": 0 },
|
||||||
|
{ "a": 6, "b": "1" },
|
||||||
|
{ "a": 7, "b": 1 },
|
||||||
|
{ "a": 8, "b": "true" },
|
||||||
|
{ "a": 9, "b": false },
|
||||||
|
{ "a": 10, "b": null },
|
||||||
|
{ "a": 11 }
|
||||||
|
],
|
||||||
|
"anything": "else"
|
||||||
|
}`
|
||||||
|
trues = Get(json, `vals.#(b==~true)#.a`).Raw
|
||||||
|
truesNOT = Get(json, `vals.#(b!=~true)#.a`).Raw
|
||||||
|
falses = Get(json, `vals.#(b==~false)#.a`).Raw
|
||||||
|
falsesNOT = Get(json, `vals.#(b!=~false)#.a`).Raw
|
||||||
|
nulls = Get(json, `vals.#(b==~null)#.a`).Raw
|
||||||
|
nullsNOT = Get(json, `vals.#(b!=~null)#.a`).Raw
|
||||||
|
exists = Get(json, `vals.#(b==~*)#.a`).Raw
|
||||||
|
existsNOT = Get(json, `vals.#(b!=~*)#.a`).Raw
|
||||||
|
assert(t, trues == "[6,7,8]")
|
||||||
|
assert(t, truesNOT == "[1,2,3,4,5,9,10,11]")
|
||||||
|
assert(t, falses == "[3,4,5,9,10,11]")
|
||||||
|
assert(t, falsesNOT == "[1,2,6,7,8]")
|
||||||
|
assert(t, nulls == "[10,11]")
|
||||||
|
assert(t, nullsNOT == "[1,2,3,4,5,6,7,8,9]")
|
||||||
|
assert(t, exists == "[1,2,3,4,5,6,7,8,9,10]")
|
||||||
|
assert(t, existsNOT == "[11]")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestModifierDoubleQuotes(t *testing.T) {
|
func TestModifierDoubleQuotes(t *testing.T) {
|
||||||
|
|
Loading…
Reference in New Issue