diff --git a/README.md b/README.md index 96b2e4d..4807517 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,20 @@

-GJSON
GoDoc GJSON Playground GJSON Syntax - +

-

get json values quickly

+

get JSON values quickly

-GJSON is a Go package that provides a [fast](#performance) and [simple](#get-a-value) way to get values from a json document. -It has features such as [one line retrieval](#get-a-value), [dot notation paths](#path-syntax), [iteration](#iterate-through-an-object-or-array), and [parsing json lines](#json-lines). +GJSON is a Go package that provides a [fast](#performance) and [simple](#get-a-value) way to get values from a JSON document. +It has features such as [one line retrieval](#get-a-value), [dot notation paths](#path-syntax), [iteration](#iterate-through-an-object-or-array), and [parsing JSON lines](#json-lines). -Also check out [SJSON](https://github.com/tidwall/sjson) for modifying json, and the [JJ](https://github.com/tidwall/jj) command line tool. +Also check out [SJSON](https://github.com/tidwall/sjson) for modifying JSON, and the [JJ](https://github.com/tidwall/jj) command-line tool. This README is a quick overview of how to use GJSON, for more information check out [GJSON Syntax](SYNTAX.md). @@ -34,7 +34,7 @@ $ go get -u github.com/tidwall/gjson This will retrieve the library. ## Get a value -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. +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. ```go package main @@ -58,7 +58,7 @@ Prichard ## Path Syntax -Below is a quick overview of the path syntax, for more complete information please +Below is a quick overview of the path syntax, for further information please check out [GJSON Syntax](SYNTAX.md). A path is a series of keys separated by a dot. @@ -93,9 +93,9 @@ The dot and wildcard characters can be escaped with '\\'. "friends.1.last" >> "Craig" ``` -You can also query an array for the first match by using `#(...)`, or find all -matches with `#(...)#`. Queries support the `==`, `!=`, `<`, `<=`, `>`, `>=` -comparison operators and the simple pattern matching `%` (like) and `!%` +You can also query an array for the first match by using `#(...)`, or find all +matches with `#(...)#`. Queries support the `==`, `!=`, `<`, `<=`, `>`, `>=` +comparison operators and the simple pattern matching `%` (like) and `!%` (not like) operators. ``` @@ -107,15 +107,15 @@ friends.#(first!%"D*").last >> "Craig" friends.#(nets.#(=="fb"))#.first >> ["Dale","Roger"] ``` -*Please note that prior to v1.3.0, queries used the `#[...]` brackets. This was +*Please note that before v1.3.0, queries used the `#[...]` brackets. This was changed in v1.3.0 as to avoid confusion with the new -[multipath](SYNTAX.md#multipaths) syntax. For backwards compatibility, +[multi-paths](SYNTAX.md#multi-paths) syntax. For backwards compatibility, `#[...]` will continue to work until the next major release.* ## Result Type -GJSON supports the json types `string`, `number`, `bool`, and `null`. -Arrays and Objects are returned as their raw json types. +GJSON supports the JSON types `string`, `number`, `bool`, and `null`. +Arrays and Objects are returned as their raw JSON types. The `Result` type holds one of these: @@ -137,7 +137,7 @@ result.Index // index of raw value in original json, zero means index u result.Indexes // indexes of all the elements that match on a path containing the '#' query character. ``` -There are a variety of handy functions that work on a result: +These are a variety of handy functions that work on a result: ```go result.Exists() bool @@ -179,17 +179,17 @@ result.Int() int64 // -9223372036854775808 to 9223372036854775807 result.Uint() uint64 // 0 to 18446744073709551615 ``` -## Modifiers and path chaining +## Modifiers and path chaining New in version 1.2 is support for modifier functions and path chaining. -A modifier is a path component that performs custom processing on the -json. +A modifier is a path component that performs custom processing on the +JSON. -Multiple paths can be "chained" together using the pipe character. +Multiple paths can be "chained" together using the pipe character. This is useful for getting results from a modified query. -For example, using the built-in `@reverse` modifier on the above json document, +For example, using the built-in `@reverse` modifier on the above JSON document, we'll get `children` array and reverse the order: ``` @@ -197,34 +197,34 @@ we'll get `children` array and reverse the order: "children|@reverse|0" >> "Jack" ``` -There are currently the following built-in modifiers: +These are currently the following built-in modifiers: - `@reverse`: Reverse an array or the members of an object. -- `@ugly`: Remove all whitespace from a json document. -- `@pretty`: Make the json document more human readable. +- `@ugly`: Remove all whitespace from a JSON document. +- `@pretty`: Make the JSON document more human-readable. - `@this`: Returns the current element. It can be used to retrieve the root element. -- `@valid`: Ensure the json document is valid. +- `@valid`: Ensure the JSON document is valid. - `@flatten`: Flattens an array. - `@join`: Joins multiple objects into a single object. - `@keys`: Returns an array of keys for an object. - `@values`: Returns an array of values for an object. -- `@tostr`: Converts json to a string. Wraps a json string. -- `@fromstr`: Converts a string from json. Unwraps a json string. +- `@tostr`: Converts JSON to a string. Wraps a JSON string. +- `@fromstr`: Converts a string from JSON. Unwraps a JSON string. - `@group`: Groups arrays of objects. See [e4fc67c](https://github.com/tidwall/gjson/commit/e4fc67c92aeebf2089fabc7872f010e340d105db). - `@dig`: Search for a value without providing its entire path. See [e8e87f2](https://github.com/tidwall/gjson/commit/e8e87f2a00dc41f3aba5631094e21f59a8cf8cbf). ### Modifier arguments -A modifier may accept an optional argument. The argument can be a valid JSON +A modifier may accept an optional argument. The argument can be a valid JSON document or just characters. -For example, the `@pretty` modifier takes a json object as its argument. +For example, the `@pretty` modifier takes a JSON object as its argument. ``` -@pretty:{"sortKeys":true} +@pretty:{"sortKeys":true} ``` -Which makes the json pretty and orders all of its keys. +Which makes the JSON pretty and orders all of its keys. ```json { @@ -240,14 +240,14 @@ Which makes the json pretty and orders all of its keys. } ``` -*The full list of `@pretty` options are `sortKeys`, `indent`, `prefix`, and `width`. +*The full list of `@pretty` options are `sortKeys`, `indent`, `prefix`, and `width`. Please see [Pretty Options](https://github.com/tidwall/pretty#customized-output) for more information.* ### Custom modifiers You can also add custom modifiers. -For example, here we create a modifier that makes the entire json document upper +For example, here we create a modifier that makes the entire JSON document upper or lower case. ```go @@ -269,7 +269,7 @@ gjson.AddModifier("case", func(json, arg string) string { ## JSON Lines -There's support for [JSON Lines](http://jsonlines.org/) using the `..` prefix, which treats a multilined document as an array. +There's support for [JSON Lines](http://jsonlines.org/) using the `..` prefix, which treats a multi-lined document as an array. For example: @@ -299,26 +299,26 @@ gjson.ForEachLine(json, func(line gjson.Result) bool{ ## Get nested array values -Suppose you want all the last names from the following json: +Suppose you want all the last names from the following JSON: ```json { "programmers": [ { - "firstName": "Janet", - "lastName": "McLaughlin", + "firstName": "Janet", + "lastName": "McLaughlin", }, { - "firstName": "Elliotte", - "lastName": "Hunter", + "firstName": "Elliotte", + "lastName": "Hunter", }, { - "firstName": "Jason", - "lastName": "Harold", + "firstName": "Jason", + "lastName": "Harold", } ] } ``` -You would use the path "programmers.#.lastName" like such: +You would use the path `programmers.#.lastName` like such: ```go result := gjson.Get(json, "programmers.#.lastName") @@ -336,7 +336,7 @@ println(name.String()) // prints "Elliotte" ## Iterate through an object or array -The `ForEach` function allows for quickly iterating through an object or array. +The `ForEach` function allows for quickly iterating through an object or array. The key and value are passed to the iterator function for objects. Only the value is passed for arrays. Returning `false` from an iterator will stop iteration. @@ -344,7 +344,7 @@ Returning `false` from an iterator will stop iteration. ```go result := gjson.Get(json, "programmers") result.ForEach(func(key, value gjson.Result) bool { - println(value.String()) + println(value.String()) return true // keep iterating }) ``` @@ -353,7 +353,7 @@ result.ForEach(func(key, value gjson.Result) bool { There's a `Parse(json)` function that will do a simple parse, and `result.Get(path)` that will search a result. -For example, all of these will return the same result: +For example, these will return the same result: ```go gjson.Parse(json).Get("name").Get("last") @@ -363,7 +363,7 @@ gjson.Get(json, "name.last") ## Check for the existence of a value -Sometimes you just want to know if a value exists. +Sometimes you just want to know if a value exists. ```go value := gjson.Get(json, "name.last") @@ -381,9 +381,9 @@ if gjson.Get(json, "name.last").Exists() { ## Validate JSON -The `Get*` and `Parse*` functions expects that the json is well-formed. Bad json will not panic, but it may return back unexpected results. +The `Get*` and `Parse*` functions expects that the JSON is well-formed. Bad JSON will not panic, but it may return unexpected results. -If you are consuming JSON from an unpredictable source then you may want to validate prior to using GJSON. +If you are consuming JSON from an unpredictable source then you may want to validate before using GJSON. ```go if !gjson.Valid(json) { @@ -412,7 +412,7 @@ var json []byte = ... result := gjson.GetBytes(json, path) ``` -If you are using the `gjson.GetBytes(json, path)` function and you want to avoid converting `result.Raw` to a `[]byte`, then you can use this pattern: +If you are using the `gjson.GetBytes(json, path)` function, and you want to avoid converting `result.Raw` to a `[]byte`, then you can use this pattern: ```go var json []byte = ... @@ -425,12 +425,12 @@ if result.Index > 0 { } ``` -This is a best-effort no allocation sub slice of the original json. This method utilizes the `result.Index` field, which is the position of the raw data in the original json. It's possible that the value of `result.Index` equals zero, in which case the `result.Raw` is converted to a `[]byte`. +This is a best-effort no allocation sub slice of the original JSON. This method utilizes the `result.Index` field, which is the position of the raw data in the original JSON. It's possible that the value of `result.Index` equals zero, in which case the `result.Raw` is converted to a `[]byte`. ## Performance -Benchmarks of GJSON alongside [encoding/json](https://golang.org/pkg/encoding/json/), -[ffjson](https://github.com/pquerna/ffjson), +Benchmarks of GJSON alongside [encoding/json](https://golang.org/pkg/encoding/json/), +[ffjson](https://github.com/pquerna/ffjson), [EasyJSON](https://github.com/mailru/easyjson), [jsonparser](https://github.com/buger/jsonparser), and [json-iterator](https://github.com/json-iterator/go) @@ -459,7 +459,7 @@ JSON document used: "width": 500, "height": 500 }, - "image": { + "image": { "src": "Images/Sun.png", "hOffset": 250, "vOffset": 250, @@ -474,7 +474,7 @@ JSON document used: "onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;" } } -} +} ``` Each operation was rotated through one of the following search paths: diff --git a/SYNTAX.md b/SYNTAX.md index a3f0fac..39b77d4 100644 --- a/SYNTAX.md +++ b/SYNTAX.md @@ -10,17 +10,17 @@ This document is designed to explain the structure of a GJSON Path through examp - [Escape Character](#escape-character) - [Arrays](#arrays) - [Queries](#queries) -- [Dot vs Pipe](#dot-vs-pipe) +- [Dot vs. Pipe](#dot-vs-pipe) - [Modifiers](#modifiers) -- [Multipaths](#multipaths) +- [Multi-paths](#multi-paths) - [Literals](#literals) -The definitive implementation is [github.com/tidwall/gjson](https://github.com/tidwall/gjson). +The definitive implementation is [github.com/tidwall/gjson](https://github.com/tidwall/gjson). Use the [GJSON Playground](https://gjson.dev) to experiment with the syntax online. ## Path structure -A GJSON Path is intended to be easily expressed as a series of components separated by a `.` character. +A GJSON Path is intended to be easily expressed as a series of components separated by a `.` character. Along with `.` character, there are a few more that have special meaning, including `|`, `#`, `@`, `\`, `*`, `!`, and `?`. @@ -44,9 +44,9 @@ Given this JSON The following GJSON Paths evaluate to the accompanying values. -### Basic +### Basic -In many cases you'll just want to retrieve values by object name or array index. +Often, you'll just want to retrieve values by object name or array index. ```go name.last "Anderson" @@ -61,7 +61,7 @@ friends.1.first "Roger" ### Wildcards -A key may contain the special wildcard characters `*` and `?`. +A key may contain the special wildcard characters `*` and `?`. The `*` will match on any zero+ characters, and `?` matches on any one character. ```go @@ -71,24 +71,24 @@ c?ildren.0 "Sara" ### Escape character -Special purpose characters, such as `.`, `*`, and `?` can be escaped with `\`. +Special purpose characters, such as `.`, `*`, and `?` can be escaped with `\`. ```go fav\.movie "Deer Hunter" ``` -You'll also need to make sure that the `\` character is correctly escaped when hardcoding a path in your source code. +You'll also need to make sure that the `\` character is correctly escaped when hard-coding a path in your source code. ```go // Go val := gjson.Get(json, "fav\\.movie") // must escape the slash -val := gjson.Get(json, `fav\.movie`) // no need to escape the slash +val := gjson.Get(json, `fav\.movie`) // no need to escape the slash ``` ```rust // Rust let val = gjson::get(json, "fav\\.movie") // must escape the slash -let val = gjson::get(json, r#"fav\.movie"#) // no need to escape the slash +let val = gjson::get(json, r#"fav\.movie"#) // no need to escape the slash ``` @@ -105,8 +105,8 @@ friends.#.age [44,68,47] ### Queries -You can also query an array for the first match by using `#(...)`, or find all matches with `#(...)#`. -Queries support the `==`, `!=`, `<`, `<=`, `>`, `>=` comparison operators, +You can also query an array for the first match by using `#(...)`, or find all matches with `#(...)#`. +Queries support the `==`, `!=`, `<`, `<=`, `>`, `>=` comparison operators, and the simple pattern matching `%` (like) and `!%` (not like) operators. ```go @@ -130,8 +130,8 @@ Nested queries are allowed. friends.#(nets.#(=="fb"))#.first >> ["Dale","Roger"] ``` -*Please note that prior to v1.3.0, queries used the `#[...]` brackets. This was -changed in v1.3.0 as to avoid confusion with the new [multipath](#multipaths) +*Please note that before v1.3.0, queries used the `#[...]` brackets. This was +changed in v1.3.0 as to avoid confusion with the new [multi-paths](#multi-paths) syntax. For backwards compatibility, `#[...]` will continue to work until the next major release.* @@ -183,11 +183,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 `|`. -In most cases they both end up returning the same results. -The cases where`|` differs from `.` is when it's used after the `#` for [Arrays](#arrays) and [Queries](#queries). +The `.` is standard separator, but it's also possible to use a `|`. +Usually, they both end up returning the same results. +The cases where`|` differs from `.` is when it's used after the `#` for [Arrays](#arrays) and [Queries](#queries). Here are some examples @@ -215,14 +215,14 @@ The path `friends.#(last="Murphy")#` all by itself results in [{"first": "Dale", "last": "Murphy", "age": 44},{"first": "Jane", "last": "Murphy", "age": 47}] ``` -The `.first` suffix will process the `first` path on each array element *before* returning the results. Which becomes +The `.first` suffix will process the `first` path on each array element *before* returning the results. Which becomes: ```json ["Dale","Jane"] ``` -But the `|first` suffix actually processes the `first` path *after* the previous result. -Since the previous result is an array, not an object, it's not possible to process +But the `|first` suffix actually processes the `first` path *after* the previous result. +Since the previous result is an array, not an object, it's impossible to process because `first` does not exist. Yet, `|0` suffix returns @@ -244,19 +244,19 @@ children.@reverse ["Jack","Alex","Sara"] children.@reverse.0 "Jack" ``` -There are currently the following built-in modifiers: +These are currently the following built-in modifiers: - `@reverse`: Reverse an array or the members of an object. - `@ugly`: Remove all whitespace from JSON. -- `@pretty`: Make the JSON more human readable. +- `@pretty`: Make the JSON more human-readable. - `@this`: Returns the current element. It can be used to retrieve the root element. -- `@valid`: Ensure the json document is valid. +- `@valid`: Ensure the JSON document is valid. - `@flatten`: Flattens an array. - `@join`: Joins multiple objects into a single object. - `@keys`: Returns an array of keys for an object. - `@values`: Returns an array of values for an object. -- `@tostr`: Converts json to a string. Wraps a json string. -- `@fromstr`: Converts a string from json. Unwraps a json string. +- `@tostr`: Converts JSON to a string. Wraps a JSON string. +- `@fromstr`: Converts a string from JSON. Unwraps a JSON string. - `@group`: Groups arrays of objects. See [e4fc67c](https://github.com/tidwall/gjson/commit/e4fc67c92aeebf2089fabc7872f010e340d105db). - `@dig`: Search for a value without providing its entire path. See [e8e87f2](https://github.com/tidwall/gjson/commit/e8e87f2a00dc41f3aba5631094e21f59a8cf8cbf). @@ -264,13 +264,13 @@ There are currently the following built-in modifiers: A modifier may accept an optional argument. The argument can be a valid JSON payload or just characters. -For example, the `@pretty` modifier takes a json object as its argument. +For example, the `@pretty` modifier takes a JSON object as its argument. ``` @pretty:{"sortKeys":true} ``` -Which makes the json pretty and orders all of its keys. +Which makes the JSON pretty and orders all its keys. ```json { @@ -286,12 +286,12 @@ Which makes the json pretty and orders all of its keys. } ``` -*The full list of `@pretty` options are `sortKeys`, `indent`, `prefix`, and `width`. +*The full list of `@pretty` options are `sortKeys`, `indent`, `prefix`, and `width`. Please see [Pretty Options](https://github.com/tidwall/pretty#customized-output) for more information.* #### Custom modifiers -You can also add custom modifiers. +You can also add custom modifiers. For example, here we create a modifier which makes the entire JSON payload upper or lower case. @@ -311,25 +311,25 @@ gjson.AddModifier("case", func(json, arg string) string { *Note: Custom modifiers are not yet available in the Rust version* -### Multipaths +### Multi-paths Starting with v1.3.0, GJSON added the ability to join multiple paths together to form new documents. Wrapping comma-separated paths between `[...]` or `{...}` will result in a new array or object, respectively. -For example, using the given multipath: +For example, using the given multi-path: ``` {name.first,age,"the_murphys":friends.#(last="Murphy")#.first} ``` -Here we selected the first name, age, and the first name for friends with the -last name "Murphy". +Here we selected the first name, age, and the first name for friends with the +last name `Murphy`. -You'll notice that an optional key can be provided, in this case -"the_murphys", to force assign a key to a value. Otherwise, the name of the -actual field will be used, in this case "first". If a name cannot be -determined, then "_" is used. +You'll notice that an optional key can be provided, in this case +`the_murphys`, to force assign a key to a value. Otherwise, the name of the +actual field will be used, in this case `first`. If a name cannot be +determined, then `_` is used. This results in @@ -339,19 +339,19 @@ This results in ### Literals -Starting with v1.12.0, GJSON added support of json literals, which provides a way for constructing static blocks of json. This is can be particularly useful when constructing a new json document using [multipaths](#multipaths). +Starting with v1.12.0, GJSON added support of JSON literals, which provides a way for constructing static blocks of JSON. This can be particularly useful when constructing a new JSON document using [multi-paths](#multi-paths). -A json literal begins with the '!' declaration character. +A JSON literal begins with the '!' declaration character. -For example, using the given multipath: +For example, using the given multi-path: ``` {name.first,age,"company":!"Happysoft","employed":!true} ``` -Here we selected the first name and age. Then add two new fields, "company" and "employed". +Here we selected the first name and age. Then add two new fields, `company` and `employed`. -This results in +This results in ```json {"first":"Tom","age":37,"company":"Happysoft","employed":true} diff --git a/gjson.go b/gjson.go index eb8c707..f9711fa 100644 --- a/gjson.go +++ b/gjson.go @@ -1,4 +1,4 @@ -// Package gjson provides searching for json strings. +// Package gjson provides searching for JSON strings. package gjson import ( @@ -17,15 +17,15 @@ import ( type Type int const ( - // Null is a null json value + // Null is a null JSON value Null Type = iota - // False is a json false boolean + // False is a JSON false boolean False - // Number is json number + // Number is JSON number Number - // String is a json string + // String is a JSON string String - // True is a json true boolean + // True is a JSON true boolean True // JSON is a raw block of JSON JSON @@ -51,17 +51,17 @@ func (t Type) String() string { } } -// Result represents a json value that is returned from Get(). +// Result represents a JSON value that is returned from Get(). type Result struct { - // Type is the json type + // Type is the JSON type Type Type - // Raw is the raw json + // Raw is the raw JSON Raw string - // Str is the json string + // Str is the JSON string Str string - // Num is the json number + // Num is the JSON number Num float64 - // Index of raw value in original json, zero means index unknown + // Index of raw value in original JSON, zero means index unknown Index int // Indexes of all the elements that match on a path containing the '#' // query character. @@ -99,7 +99,7 @@ func (t Result) String() string { } } -// Bool returns an boolean representation. +// Bool returns a boolean representation. func (t Result) Bool() bool { switch t.Type { default: @@ -166,7 +166,7 @@ func (t Result) Uint() uint64 { } } -// Float returns an float64 representation. +// Float returns a float64 representation. func (t Result) Float() float64 { switch t.Type { default: @@ -334,7 +334,7 @@ type arrayOrMapResult struct { } func (t Result) arrayOrMap(vc byte, valueize bool) (r arrayOrMapResult) { - var json = t.Raw + json := t.Raw var i int var value Result var count int @@ -455,10 +455,10 @@ end: return } -// Parse parses the json and returns a result. +// Parse parses the JSON and returns a result. // -// 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. +// This function expects that the JSON is well-formed, and does not validate. +// Invalid JSON will not panic, but it may return unexpected results. // If you are consuming JSON from an unpredictable source then you may want to // use the Valid function first. func Parse(json string) Result { @@ -480,7 +480,7 @@ func Parse(json string) Result { value.Raw, value.Num = tonum(json[i:]) case 'n': if i+1 < len(json) && json[i+1] != 'u' { - // nan + // NaN value.Type = Number value.Raw, value.Num = tonum(json[i:]) } else { @@ -508,7 +508,7 @@ func Parse(json string) Result { return value } -// ParseBytes parses the json and returns a result. +// 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)) @@ -686,7 +686,7 @@ func (t Result) Value() interface{} { } func parseString(json string, i int) (int, string, bool, bool) { - var s = i + s := i for ; i < len(json); i++ { if json[i] > '\\' { continue @@ -724,7 +724,7 @@ func parseString(json string, i int) (int, string, bool, bool) { } func parseNumber(json string, i int) (int, string) { - var s = i + s := i i++ for ; i < len(json); i++ { if json[i] <= ' ' || json[i] == ',' || json[i] == ']' || @@ -736,7 +736,7 @@ func parseNumber(json string, i int) (int, string) { } func parseLiteral(json string, i int) (int, string) { - var s = i + s := i i++ for ; i < len(json); i++ { if json[i] < 'a' || json[i] > 'z' { @@ -793,8 +793,7 @@ func parseArrayPath(path string) (r arrayPathResult) { } else if path[1] == '[' || path[1] == '(' { // query r.query.on = true - qpath, op, value, _, fi, vesc, ok := - parseQuery(path[i:]) + qpath, op, value, _, fi, vesc, ok := parseQuery(path[i:]) if !ok { // bad query, end now break @@ -1103,7 +1102,7 @@ func parseObject(c *parseContext, i int, path string) (int, bool) { // this is slightly different from getting s string value // because we don't need the outer quotes. i++ - var s = i + s := i for ; i < len(c.json); i++ { if c.json[i] > '\\' { continue @@ -1400,6 +1399,7 @@ func queryMatches(rp *arrayPathResult, value Result) bool { } return false } + func parseArray(c *parseContext, i int, path string) (int, bool) { var pmatch, vesc, ok, hit bool var val string @@ -1619,8 +1619,8 @@ func parseArray(c *parseContext, i int, path string) (int, bool) { c.pipe = right c.piped = true } - var indexes = make([]int, 0, 64) - var jsons = make([]byte, 0, 64) + indexes := make([]int, 0, 64) + jsons := make([]byte, 0, 64) jsons = append(jsons, '[') for j, k := 0, 0; j < len(alog); j++ { idx := alog[j] @@ -1813,10 +1813,10 @@ type subSelector struct { path string } -// parseSubSelectors returns the subselectors belonging to a '[path1,path2]' or +// parseSubSelectors returns the sub-selectors 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. +// before calling this function. func parseSubSelectors(path string) (sels []subSelector, out string, ok bool) { modifier := 0 depth := 1 @@ -1978,7 +1978,7 @@ type parseContext struct { lines bool } -// Get searches json for the specified path. +// 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. // @@ -2007,8 +2007,8 @@ type parseContext struct { // "c?ildren.0" >> "Sara" // "friends.#.first" >> ["James","Roger"] // -// 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. +// This function expects that the JSON is well-formed, and does not validate. +// Invalid JSON will not panic, but it may return unexpected results. // If you are consuming JSON from an unpredictable source then you may want to // use the Valid function first. func Get(json, path string) Result { @@ -2035,7 +2035,7 @@ func Get(json, path string) Result { } } if path[0] == '[' || path[0] == '{' { - // using a subselector path + // using a sub-selector path kind := path[0] var ok bool var subs []subSelector @@ -2095,7 +2095,7 @@ func Get(json, path string) Result { } } var i int - var c = &parseContext{json: json} + c := &parseContext{json: json} if len(path) >= 2 && path[0] == '.' && path[1] == '.' { c.lines = true parseArray(c, 0, path[2:]) @@ -2122,13 +2122,13 @@ func Get(json, path string) Result { return c.value } -// GetBytes searches json for the specified path. +// 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 { return getBytes(json, path) } -// runeit returns the rune from the the \uXXXX +// runeit returns the rune from the \uXXXX func runeit(json string) rune { n, _ := strconv.ParseUint(json[:4], 16, 64) return rune(n) @@ -2136,7 +2136,7 @@ func runeit(json string) rune { // unescape unescapes a string func unescape(json string) string { - var str = make([]byte, 0, len(json)) + str := make([]byte, 0, len(json)) for i := 0; i < len(json); i++ { switch { default: @@ -2254,8 +2254,8 @@ func stringLessInsensitive(a, b string) bool { return len(a) < len(b) } -// parseAny parses the next value from a json string. -// A Result is returned when the hit param is set. +// parseAny parses the next value from a JSON string. +// A Result is returned when the hit parameter 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 @@ -2332,7 +2332,7 @@ func parseAny(json string, i int, hit bool) (int, Result, bool) { return i, res, false } -// GetMany searches json for the multiple paths. +// 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. func GetMany(json string, path ...string) []Result { @@ -2343,7 +2343,7 @@ func GetMany(json string, path ...string) []Result { return res } -// GetManyBytes searches json for the multiple paths. +// 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 { @@ -2377,6 +2377,7 @@ func validpayload(data []byte, i int) (outi int, ok bool) { } return i, false } + func validany(data []byte, i int) (outi int, ok bool) { for ; i < len(data); i++ { switch data[i] { @@ -2402,6 +2403,7 @@ func validany(data []byte, i int) (outi int, ok bool) { } return i, false } + func validobject(data []byte, i int) (outi int, ok bool) { for ; i < len(data); i++ { switch data[i] { @@ -2444,6 +2446,7 @@ func validobject(data []byte, i int) (outi int, ok bool) { } return i, false } + func validcolon(data []byte, i int) (outi int, ok bool) { for ; i < len(data); i++ { switch data[i] { @@ -2457,6 +2460,7 @@ func validcolon(data []byte, i int) (outi int, ok bool) { } return i, false } + func validcomma(data []byte, i int, end byte) (outi int, ok bool) { for ; i < len(data); i++ { switch data[i] { @@ -2472,6 +2476,7 @@ func validcomma(data []byte, i int, end byte) (outi int, ok bool) { } return i, false } + func validarray(data []byte, i int) (outi int, ok bool) { for ; i < len(data); i++ { switch data[i] { @@ -2495,6 +2500,7 @@ func validarray(data []byte, i int) (outi int, ok bool) { } return i, false } + func validstring(data []byte, i int) (outi int, ok bool) { for ; i < len(data); i++ { if data[i] < ' ' { @@ -2527,6 +2533,7 @@ func validstring(data []byte, i int) (outi int, ok bool) { } return i, false } + func validnumber(data []byte, i int) (outi int, ok bool) { i-- // sign @@ -2609,6 +2616,7 @@ func validtrue(data []byte, i int) (outi int, ok bool) { } return i, false } + func validfalse(data []byte, i int) (outi int, ok bool) { if i+4 <= len(data) && data[i] == 'a' && data[i+1] == 'l' && data[i+2] == 's' && data[i+3] == 'e' { @@ -2616,6 +2624,7 @@ func validfalse(data []byte, i int) (outi int, ok bool) { } return i, false } + func validnull(data []byte, i int) (outi int, ok bool) { if i+3 <= len(data) && data[i] == 'u' && data[i+1] == 'l' && data[i+2] == 'l' { @@ -2624,7 +2633,7 @@ func validnull(data []byte, i int) (outi int, ok bool) { return i, false } -// Valid returns true if the input is valid json. +// Valid returns true if the input is valid JSON. // // if !gjson.Valid(json) { // return errors.New("invalid json") @@ -2635,7 +2644,7 @@ func Valid(json string) bool { return ok } -// ValidBytes returns true if the input is valid json. +// ValidBytes returns true if the input is valid JSON. // // if !gjson.Valid(json) { // return errors.New("invalid json") @@ -2758,7 +2767,7 @@ func execModifier(json, path string) (pathOut, res string, ok bool) { var parsedArgs bool switch pathOut[0] { case '{', '[', '"': - // json arg + // JSON argument res := Parse(pathOut) if res.Exists() { args = squash(pathOut) @@ -2767,7 +2776,7 @@ func execModifier(json, path string) (pathOut, res string, ok bool) { } } if !parsedArgs { - // simple arg + // simple argument i := 0 for ; i < len(pathOut); i++ { if pathOut[i] == '|' { @@ -2788,7 +2797,7 @@ func execModifier(json, path string) (pathOut, res string, ok bool) { return pathOut, res, false } -// unwrap removes the '[]' or '{}' characters around json +// unwrap removes the '[]' or '{}' characters around JSON func unwrap(json string) string { json = trim(json) if len(json) >= 2 && (json[0] == '[' || json[0] == '{') { @@ -2853,7 +2862,7 @@ func cleanWS(s string) string { return s } -// @pretty modifier makes the json look nice. +// @pretty modifier makes the JSON look nice. func modPretty(json, arg string) string { if len(arg) > 0 { opts := *pretty.DefaultOptions @@ -2931,11 +2940,11 @@ func modReverse(json, arg string) string { // // [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} argument can be provided 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. +// The original JSON is returned when the JSON is not an array. func modFlatten(json, arg string) string { res := Parse(json) if !res.IsArray() { @@ -3044,7 +3053,7 @@ func modValues(json, arg string) string { // // [{"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 { res := Parse(json) if !res.IsArray() { @@ -3106,9 +3115,9 @@ func modJoin(json, arg string) string { 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 { +// @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, _ string) string { if !Valid(json) { return "" } @@ -3180,9 +3189,9 @@ type sliceHeader struct { cap int } -// getBytes casts the input json bytes to a string and safely returns the +// 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. +// copies and allocations for the large JSON string->[]byte. func getBytes(json []byte, path string) Result { var result Result if json != nil { @@ -3212,11 +3221,11 @@ func getBytes(json []byte, path string) Result { } else if uintptr(strh.data) >= uintptr(rawh.data) && uintptr(strh.data)+uintptr(strh.len) <= uintptr(rawh.data)+uintptr(rawh.len) { - // Str is a substring of Raw. + // Str is a sub-string of Raw. start := uintptr(strh.data) - uintptr(rawh.data) // safely copy the raw slice header result.Raw = string(*(*[]byte)(unsafe.Pointer(&rawh))) - // substring the raw + // sub-string the raw result.Str = result.Raw[start : start+uintptr(strh.len)] } else { // safely copy both the raw and str slice headers to strings @@ -3310,10 +3319,10 @@ func revSquash(json string) string { // // ["friends.0.first","friends.1.first","friends.2.first"] // -// The param 'json' must be the original JSON used when calling Get. +// The parameter 'json' must be the original JSON used when calling Get. // // Returns an empty string if the paths cannot be determined, which can happen -// when the Result came from a path that contained a multipath, modifier, +// when the Result came from a path that contained a multi-path, modifier, // or a nested query. func (t Result) Paths(json string) []string { if t.Indexes == nil { @@ -3339,10 +3348,10 @@ func (t Result) Paths(json string) []string { // // "friends.0" // -// The param 'json' must be the original JSON used when calling Get. +// The parameter 'json' must be the original JSON used when calling Get. // // Returns an empty string if the paths cannot be determined, which can happen -// when the Result came from a path that contained a multipath, modifier, +// when the Result came from a path that contained a multi-path, modifier, // or a nested query. func (t Result) Path(json string) string { var path []byte diff --git a/gjson_test.go b/gjson_test.go index 61c21f9..8e68755 100644 --- a/gjson_test.go +++ b/gjson_test.go @@ -107,7 +107,7 @@ func TestEscapePath(t *testing.T) { testEscapePath(t, json, "test.keyk\\*.key\\?", "val7") } -// this json block is poorly formed on purpose. +// this JSON block is poorly formed on purpose. var basicJSON = ` {"age":100, "name":{"here":"B\\\"R"}, "noop":{"what is a wren?":"a bird"}, "happy":true,"immortal":false, @@ -199,7 +199,6 @@ func TestPath(t *testing.T) { get("loggy.programmers.2.email") get("lastly.end\\.\\.\\.ing") get("lastly.yay") - } func TestTimeResult(t *testing.T) { @@ -216,8 +215,10 @@ func TestParseAny(t *testing.T) { func TestManyVariousPathCounts(t *testing.T) { json := `{"a":"a","b":"b","c":"c"}` - counts := []int{3, 4, 7, 8, 9, 15, 16, 17, 31, 32, 33, 63, 64, 65, 127, - 128, 129, 255, 256, 257, 511, 512, 513} + counts := []int{ + 3, 4, 7, 8, 9, 15, 16, 17, 31, 32, 33, 63, 64, 65, 127, + 128, 129, 255, 256, 257, 511, 512, 513, + } paths := []string{"a", "b", "c"} expects := []string{"a", "b", "c"} for _, count := range counts { @@ -238,6 +239,7 @@ func TestManyVariousPathCounts(t *testing.T) { } } } + func TestManyRecursion(t *testing.T) { var json string var path string @@ -252,6 +254,7 @@ func TestManyRecursion(t *testing.T) { path = path[1:] assert(t, GetMany(json, path)[0].String() == "b") } + func TestByteSafety(t *testing.T) { jsonb := []byte(`{"name":"Janet","age":38}`) mtok := GetBytes(jsonb, "name") @@ -348,8 +351,9 @@ func TestPlus53BitInts(t *testing.T) { // flip the number to the negative sign. assert(t, Get(json, "overflow_int64").Int() == -9223372036854775808) } + func TestIssue38(t *testing.T) { - // These should not fail, even though the unicode is invalid. + // These should not fail, even though the Unicode is invalid. Get(`["S3O PEDRO DO BUTI\udf93"]`, "0") Get(`["S3O PEDRO DO BUTI\udf93asdf"]`, "0") Get(`["S3O PEDRO DO BUTI\udf93\u"]`, "0") @@ -359,6 +363,7 @@ func TestIssue38(t *testing.T) { Get(`["S3O PEDRO DO BUTI\udf93\u1345"]`, "0") Get(`["S3O PEDRO DO BUTI\udf93\u1345asd"]`, "0") } + func TestTypes(t *testing.T) { assert(t, (Result{Type: String}).Type.String() == "String") assert(t, (Result{Type: Number}).Type.String() == "Number") @@ -404,6 +409,7 @@ func TestTypes(t *testing.T) { assert(t, (Result{Type: False}).Float() == 0) assert(t, (Result{Type: Number, Num: 1}).Float() == 1) } + func TestForEach(t *testing.T) { Result{}.ForEach(nil) Result{Type: String, Str: "Hello"}.ForEach(func(_, value Result) bool { @@ -423,6 +429,7 @@ func TestForEach(t *testing.T) { ParseBytes([]byte(`{"bad`)).ForEach(nil) ParseBytes([]byte(`{"ok":"bad`)).ForEach(nil) } + func TestMap(t *testing.T) { assert(t, len(ParseBytes([]byte(`"asdf"`)).Map()) == 0) assert(t, ParseBytes([]byte(`{"asdf":"ghjk"`)).Map()["asdf"].String() == @@ -431,6 +438,7 @@ func TestMap(t *testing.T) { assert(t, Result{Type: JSON, Raw: "**invalid**"}.Value() == nil) assert(t, Result{Type: JSON, Raw: "{"}.Map() != nil) } + func TestBasic1(t *testing.T) { mtok := get(basicJSON, `loggy.programmers`) var count int @@ -474,6 +482,7 @@ func TestBasic1(t *testing.T) { t.Fatalf("expected %v, got %v", 3, count) } } + func TestBasic2(t *testing.T) { mtok := get(basicJSON, `loggy.programmers.#[age=101].firstName`) if mtok.String() != "1002.3" { @@ -509,6 +518,7 @@ func TestBasic2(t *testing.T) { mtok.Map()["programmers"].Array()[1].Map()["firstName"].Str) } } + func TestBasic3(t *testing.T) { var mtok Result if Parse(basicJSON).Get("loggy.programmers").Get("1"). @@ -549,6 +559,7 @@ func TestBasic3(t *testing.T) { t.Fatalf("expected 0, got %v", len(mtok.Array())) } } + func TestBasic4(t *testing.T) { if get(basicJSON, "items.3.tags.#").Num != 3 { t.Fatalf("expected 3, got %v", get(basicJSON, "items.3.tags.#").Num) @@ -593,6 +604,7 @@ func TestBasic4(t *testing.T) { t.Fatal("should be nil") } } + func TestBasic5(t *testing.T) { token := get(basicJSON, "age") if token.String() != "100" { @@ -630,8 +642,9 @@ func TestBasic5(t *testing.T) { t.Fatalf("expecting %v, got %v", "Jason", fn) } } + func TestUnicode(t *testing.T) { - var json = `{"key":0,"的情况下解":{"key":1,"的情况":2}}` + json := `{"key":0,"的情况下解":{"key":1,"的情况":2}}` if Get(json, "的情况下解.key").Num != 1 { t.Fatal("fail") } @@ -659,11 +672,13 @@ func TestUnescape(t *testing.T) { unescape(string([]byte{'\\', '\\', 0})) unescape(string([]byte{'\\', '/', '\\', 'b', '\\', 'f'})) } + func assert(t testing.TB, cond bool) { if !cond { panic("assert failed") } } + func TestLess(t *testing.T) { assert(t, !Result{Type: Null}.Less(Result{Type: Null}, true)) assert(t, Result{Type: Null}.Less(Result{Type: False}, true)) @@ -673,18 +688,30 @@ func TestLess(t *testing.T) { assert(t, Result{Type: Null}.Less(Result{Type: String}, true)) assert(t, !Result{Type: False}.Less(Result{Type: Null}, true)) assert(t, Result{Type: False}.Less(Result{Type: True}, true)) - assert(t, Result{Type: String, Str: "abc"}.Less(Result{Type: String, - Str: "bcd"}, true)) - assert(t, Result{Type: String, Str: "ABC"}.Less(Result{Type: String, - Str: "abc"}, true)) - assert(t, !Result{Type: String, Str: "ABC"}.Less(Result{Type: String, - Str: "abc"}, false)) - assert(t, Result{Type: Number, Num: 123}.Less(Result{Type: Number, - Num: 456}, true)) - assert(t, !Result{Type: Number, Num: 456}.Less(Result{Type: Number, - Num: 123}, true)) - assert(t, !Result{Type: Number, Num: 456}.Less(Result{Type: Number, - Num: 456}, true)) + assert(t, Result{Type: String, Str: "abc"}.Less(Result{ + Type: String, + Str: "bcd", + }, true)) + assert(t, Result{Type: String, Str: "ABC"}.Less(Result{ + Type: String, + Str: "abc", + }, true)) + assert(t, !Result{Type: String, Str: "ABC"}.Less(Result{ + Type: String, + Str: "abc", + }, false)) + assert(t, Result{Type: Number, Num: 123}.Less(Result{ + Type: Number, + Num: 456, + }, true)) + assert(t, !Result{Type: Number, Num: 456}.Less(Result{ + Type: Number, + Num: 123, + }, true)) + assert(t, !Result{Type: Number, Num: 456}.Less(Result{ + Type: Number, + Num: 456, + }, true)) assert(t, stringLessInsensitive("abcde", "BBCDE")) assert(t, stringLessInsensitive("abcde", "bBCDE")) assert(t, stringLessInsensitive("Abcde", "BBCDE")) @@ -776,7 +803,7 @@ var exampleJSON = `{ }` func TestUnmarshalMap(t *testing.T) { - var m1 = Parse(exampleJSON).Value().(map[string]interface{}) + m1 := Parse(exampleJSON).Value().(map[string]interface{}) var m2 map[string]interface{} if err := json.Unmarshal([]byte(exampleJSON), &m2); err != nil { t.Fatal(err) @@ -795,9 +822,9 @@ func TestUnmarshalMap(t *testing.T) { } func TestSingleArrayValue(t *testing.T) { - var json = `{"key": "value","key2":[1,2,3,4,"A"]}` - var result = Get(json, "key") - var array = result.Array() + json := `{"key": "value","key2":[1,2,3,4,"A"]}` + result := Get(json, "key") + array := result.Array() if len(array) != 1 { t.Fatal("array is empty") } @@ -814,7 +841,6 @@ func TestSingleArrayValue(t *testing.T) { if len(array) != 0 { t.Fatalf("got '%v', expected '%v'", len(array), 0) } - } var manyJSON = ` { @@ -867,12 +893,15 @@ func TestManyBasic(t *testing.T) { testMany(true, `[Cat Nancy]`, "name\\.first", "name.first") testMany(true, `[world]`, strings.Repeat("a.", 70)+"hello") } + func testMany(t *testing.T, json string, paths, expected []string) { testManyAny(t, json, paths, expected, true) testManyAny(t, json, paths, expected, false) } + func testManyAny(t *testing.T, json string, paths, expected []string, - bytes bool) { + bytes bool, +) { var result []Result for i := 0; i < 2; i++ { var which string @@ -902,6 +931,7 @@ func testManyAny(t *testing.T, json string, paths, expected []string, } } } + func TestIssue20(t *testing.T) { json := `{ "name": "FirstName", "name1": "FirstName1", ` + `"address": "address1", "addressDetails": "address2", }` @@ -918,10 +948,14 @@ func TestIssue21(t *testing.T) { "Level1Field4":4, "Level1Field2":{ "Level2Field1":[ "value1", "value2" ], "Level2Field2":{ "Level3Field1":[ { "key1":"value1" } ] } } }` - paths := []string{"Level1Field1", "Level1Field2.Level2Field1", - "Level1Field2.Level2Field2.Level3Field1", "Level1Field4"} - expected := []string{"3", `[ "value1", "value2" ]`, - `[ { "key1":"value1" } ]`, "4"} + paths := []string{ + "Level1Field1", "Level1Field2.Level2Field1", + "Level1Field2.Level2Field2.Level3Field1", "Level1Field4", + } + expected := []string{ + "3", `[ "value1", "value2" ]`, + `[ { "key1":"value1" } ]`, "4", + } t.Run("SingleMany", func(t *testing.T) { testMany(t, json, paths, expected) @@ -1097,8 +1131,10 @@ func TestValidBasic(t *testing.T) { testvalid(t, "[-.123]", false) } -var jsonchars = []string{"{", "[", ",", ":", "}", "]", "1", "0", "true", - "false", "null", `""`, `"\""`, `"a"`} +var jsonchars = []string{ + "{", "[", ",", ":", "}", "]", "1", "0", "true", + "false", "null", `""`, `"\""`, `"a"`, +} func makeRandomJSONChars(b []byte) { var bb []byte @@ -1217,6 +1253,7 @@ func TestIssue55(t *testing.T) { } } } + func TestIssue58(t *testing.T) { json := `{"data":[{"uid": 1},{"uid": 2}]}` res := Get(json, `data.#[uid!=1]`).Raw @@ -1279,11 +1316,10 @@ null if i != 4 { t.Fatalf("expected '%v', got '%v'", 4, i) } - } func TestNumUint64String(t *testing.T) { - var i int64 = 9007199254740993 //2^53 + 1 + var i int64 = 9007199254740993 // 2^53 + 1 j := fmt.Sprintf(`{"data": [ %d, "hello" ] }`, i) res := Get(j, "data.0") if res.String() != "9007199254740993" { @@ -1312,7 +1348,7 @@ func TestNumBigString(t *testing.T) { func TestNumFloatString(t *testing.T) { var i int64 = -9007199254740993 - j := fmt.Sprintf(`{"data":[ "hello", %d ]}`, i) //No quotes around value!! + j := fmt.Sprintf(`{"data":[ "hello", %d ]}`, i) // No quotes around value!! res := Get(j, "data.1") if res.String() != "-9007199254740993" { t.Fatalf("expected '%v', got '%v'", "-9007199254740993", res.String()) @@ -1321,7 +1357,7 @@ func TestNumFloatString(t *testing.T) { func TestDuplicateKeys(t *testing.T) { // this is valid json according to the JSON spec - var json = `{"name": "Alex","name": "Peter"}` + json := `{"name": "Alex","name": "Peter"}` if Parse(json).Get("name").String() != Parse(json).Map()["name"].String() { t.Fatalf("expected '%v', got '%v'", @@ -1335,7 +1371,7 @@ func TestDuplicateKeys(t *testing.T) { } func TestArrayValues(t *testing.T) { - var json = `{"array": ["PERSON1","PERSON2",0],}` + json := `{"array": ["PERSON1","PERSON2",0],}` values := Get(json, "array").Array() var output string for i, val := range values { @@ -1354,7 +1390,6 @@ func TestArrayValues(t *testing.T) { if output != expect { t.Fatalf("expected '%v', got '%v'", expect, output) } - } func BenchmarkValid(b *testing.B) { @@ -1459,7 +1494,6 @@ func TestSplitPipe(t *testing.T) { split(t, `hello.#[a|1="asdf\"|1324"]#|that.more|yikes`, `hello.#[a|1="asdf\"|1324"]#`, "that.more|yikes", true) split(t, `a.#[]#\|b`, "", "", false) - } func TestArrayEx(t *testing.T) { @@ -1705,7 +1739,6 @@ func TestQueries(t *testing.T) { assert(t, Get(json, `i*.f*.#[cust1>=true].first`).Exists()) assert(t, !Get(json, `i*.f*.#[cust2