[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#1032862: unblock: golang-github-tidwall-gjson/1.14.4-2 (pre-approval)



Control: tags -1 confirmed

On 2023-03-13 00:40:23 +0100, Cyril Brulebois wrote:
> Package: release.debian.org
> Severity: normal
> User: release.debian.org@packages.debian.org
> Usertags: unblock
> X-Debbugs-Cc: debian-go@lists.debian.org
> 
> Hi,
> 
> Please consider ACKing a golang-github-tidwall-gjson upload catching up
> with newer upstream releases.
> 
> [ Reason ]
> The golang-github-tidwall-gjson package currently in testing and
> unstable suffers from at least two CVEs (#1000225, #1011616).
> 
> Currently, we have a 1.6.7 version, those bugs are supposed to be fixed
> in 1.9.x, and upstream is at 1.14.4…
> 
> This library is about parsing JSON, is basically one big Go file (along
> with another one for the tests).
> 
> Since that's about parsing things, I suppose it wouldn't be trivial to
> backport the security fixes from 1.9.2 and 1.9.3 without understanding
> how parsing works, and why it was buggy in 1.6.7. Shipping the latest
> 1.9.x would probably be safer. But then, if we're going to have a bump
> in upstream releases, it seemed (at least to Thorsten Alteholz on the
> debian-go@ list and to me) that considering the latest would make most
> sense. We would get those fixes, possible other ones, and that would
> minimize the delta whenever other security fixes come up.
> 
> The reverse dependencies are somewhat limited:
>  - dak lists 3 packages via Depends;
>  - dak lists 5 packages via Build-Depends;
>  - ratt finds 14 packages when it's time to rebuild all the things.
> 
> [ Impact ]
> I'm not sure I would be able to backport security fixes (at all, or
> properly), and failing to get a fixed package into testing might get a
> bunch of packages kicked out. This includes crowdsec, which is my
> primary concern when it comes to Go packages.
> 
> [ Tests ]
> ratt has been used to check that all 14 identified packages still build
> fine. Those are Go packages, so they usually come with a test suite (but
> I must admit I didn't check each one individually).
> 
> Additionally, I've uploaded 1.14.4-1 to experimental to benefit from the
> automated autopkgtest runs (for Go packages that means building/testing
> on the considered arch), and those haven't uncovered any issues on any
> of the ci.debian.net archs, which is an extra reassurance compared to my
> initial build tests via ratt, only on amd64.
> 
> [ Risks ]
> No regressions have been spotted thus far, either in the package or its
> reverse dependencies, and I'm signing up for investigating anything that
> might come up as a side effect of this update.

Thanks for performing all the tests and confirming that this should be a
low risk upload. It's still a big diff, but given since this a library
for parsing potentially untrusted input, let's take the new upstream
version with the CVE fixes.

So: please go ahead with the upload.

> [ Checklist ]
>   [x] all changes are documented in the d/changelog
>   [x] I reviewed all changes and I approve them
>   [x] attach debdiff against the package in testing
> 
> [ Other info ]
> This is a Go package, so reverse dependencies will need to be rebuilt
> against the updated code. That being said, with the binNMU rounds
> happening to avoid keeping too many `Extra-Source-Only: yes` packages
> in testing, that might just happen automatically without requiring
> manual scheduling.

Indeed, it will be rebuilt on the next round of outdated ESO builds.

Cheers

> unblock golang-github-tidwall-gjson/1.14.4-2
> 
> 
> Cheers,
> -- 
> Cyril Brulebois -- Debian Consultant @ DEBAMAX -- https://debamax.com/

> diff --git a/README.md b/README.md
> index 8553273..c8db11f 100644
> --- a/README.md
> +++ b/README.md
> @@ -4,7 +4,9 @@
>      width="240" height="78" border="0" alt="GJSON">
>  <br>
>  <a href="https://godoc.org/github.com/tidwall/gjson";><img src="https://img.shields.io/badge/api-reference-blue.svg?style=flat-square"; alt="GoDoc"></a>
> -<a href="http://tidwall.com/gjson-play";><img src="https://img.shields.io/badge/%F0%9F%8F%90-playground-9900cc.svg?style=flat-square"; alt="GJSON Playground"></a>
> +<a href="https://tidwall.com/gjson-play";><img src="https://img.shields.io/badge/%F0%9F%8F%90-playground-9900cc.svg?style=flat-square"; alt="GJSON Playground"></a>
> +<a href="SYNTAX.md"><img src="https://img.shields.io/badge/{}-syntax-33aa33.svg?style=flat-square"; alt="GJSON Syntax"></a>
> +	
>  </p>
>  
>  <p align="center">get json values quickly</a></p>
> @@ -14,6 +16,10 @@ It has features such as [one line retrieval](#get-a-value), [dot notation paths]
>  
>  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).
> +
> +GJSON is also available for [Python](https://github.com/volans-/gjson-py) and [Rust](https://github.com/tidwall/gjson.rs)
> +
>  Getting Started
>  ===============
>  
> @@ -123,11 +129,12 @@ nil, for JSON null
>  To directly access the value:
>  
>  ```go
> -result.Type    // can be String, Number, True, False, Null, or JSON
> -result.Str     // holds the string
> -result.Num     // holds the float64 number
> -result.Raw     // holds the raw json
> -result.Index   // index of raw value in original json, zero means index unknown
> +result.Type           // can be String, Number, True, False, Null, or JSON
> +result.Str            // holds the string
> +result.Num            // holds the float64 number
> +result.Raw            // holds the raw json
> +result.Index          // index of raw value in original json, zero means index unknown
> +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:
> @@ -150,10 +157,6 @@ result.Less(token Result, caseSensitive bool) bool
>  
>  The `result.Value()` function returns an `interface{}` which requires type assertion and is one of the following Go types:
>  
> -The `result.Array()` function returns back an array of values.
> -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.
> -
>  ```go
>  boolean >> bool
>  number  >> float64
> @@ -163,13 +166,17 @@ array   >> []interface{}
>  object  >> map[string]interface{}
>  ```
>  
> +The `result.Array()` function returns back an array of values.
> +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.
> +
>  ### 64-bit integers
>  
>  The `result.Int()` and `result.Uint()` calls are capable of reading all 64 bits, allowing for large JSON integers.
>  
>  ```go
>  result.Int() int64    // -9223372036854775808 to 9223372036854775807
> -result.Uint() int64   // 0 to 18446744073709551615
> +result.Uint() uint64   // 0 to 18446744073709551615
>  ```
>  
>  ## Modifiers and path chaining 
> @@ -199,6 +206,11 @@ There are currently the following built-in modifiers:
>  - `@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.
> +- `@group`: Groups arrays of objects. See [e4fc67c](https://github.com/tidwall/gjson/commit/e4fc67c92aeebf2089fabc7872f010e340d105db).
>  
>  ### Modifier arguments
>  
> @@ -433,14 +445,15 @@ Benchmarks of GJSON alongside [encoding/json](https://golang.org/pkg/encoding/js
>  and [json-iterator](https://github.com/json-iterator/go)
>  
>  ```
> -BenchmarkGJSONGet-8                  3000000        372 ns/op          0 B/op         0 allocs/op
> -BenchmarkGJSONUnmarshalMap-8          900000       4154 ns/op       1920 B/op        26 allocs/op
> -BenchmarkJSONUnmarshalMap-8           600000       9019 ns/op       3048 B/op        69 allocs/op
> -BenchmarkJSONDecoder-8                300000      14120 ns/op       4224 B/op       184 allocs/op
> -BenchmarkFFJSONLexer-8               1500000       3111 ns/op        896 B/op         8 allocs/op
> -BenchmarkEasyJSONLexer-8             3000000        887 ns/op        613 B/op         6 allocs/op
> -BenchmarkJSONParserGet-8             3000000        499 ns/op         21 B/op         0 allocs/op
> -BenchmarkJSONIterator-8              3000000        812 ns/op        544 B/op         9 allocs/op
> +BenchmarkGJSONGet-16                11644512       311 ns/op       0 B/op	       0 allocs/op
> +BenchmarkGJSONUnmarshalMap-16        1122678      3094 ns/op    1920 B/op	      26 allocs/op
> +BenchmarkJSONUnmarshalMap-16          516681      6810 ns/op    2944 B/op	      69 allocs/op
> +BenchmarkJSONUnmarshalStruct-16       697053      5400 ns/op     928 B/op	      13 allocs/op
> +BenchmarkJSONDecoder-16               330450     10217 ns/op    3845 B/op	     160 allocs/op
> +BenchmarkFFJSONLexer-16              1424979      2585 ns/op     880 B/op	       8 allocs/op
> +BenchmarkEasyJSONLexer-16            3000000       729 ns/op     501 B/op	       5 allocs/op
> +BenchmarkJSONParserGet-16            3000000       366 ns/op      21 B/op	       0 allocs/op
> +BenchmarkJSONIterator-16             3000000       869 ns/op     693 B/op	      14 allocs/op
>  ```
>  
>  JSON document used:
> @@ -481,12 +494,4 @@ widget.image.hOffset
>  widget.text.onMouseUp
>  ```
>  
> -*These benchmarks were run on a MacBook Pro 15" 2.8 GHz Intel Core i7 using Go 1.8 and can be found [here](https://github.com/tidwall/gjson-benchmarks).*
> -
> -
> -## Contact
> -Josh Baker [@tidwall](http://twitter.com/tidwall)
> -
> -## License
> -
> -GJSON source code is available under the MIT [License](/LICENSE).
> +*These benchmarks were run on a MacBook Pro 16" 2.4 GHz Intel Core i9 using Go 1.17 and can be found [here](https://github.com/tidwall/gjson-benchmarks).*
> diff --git a/SYNTAX.md b/SYNTAX.md
> index 5ea0407..7a9b6a2 100644
> --- a/SYNTAX.md
> +++ b/SYNTAX.md
> @@ -13,16 +13,16 @@ This document is designed to explain the structure of a GJSON Path through examp
>  - [Dot vs Pipe](#dot-vs-pipe)
>  - [Modifiers](#modifiers)
>  - [Multipaths](#multipaths)
> +- [Literals](#literals)
>  
>  The definitive implemenation 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 seperated by a `.` character. 
>  
> -Along with `.` character, there are a few more that have special meaning, including `|`, `#`, `@`, `\`, `*`, and `?`.
> +Along with `.` character, there are a few more that have special meaning, including `|`, `#`, `@`, `\`, `*`, `!`, and `?`.
>  
>  ## Example
>  
> @@ -77,14 +77,21 @@ Special purpose characters, such as `.`, `*`, and `?` can be escaped with `\`.
>  fav\.movie             "Deer Hunter"
>  ```
>  
> -You'll also need to make sure that the `\` character is correctly escaped when hardcoding a path in source code.
> +You'll also need to make sure that the `\` character is correctly escaped when hardcoding a path in your source code.
>  
>  ```go
> -res := gjson.Get(json, "fav\\.movie")  // must escape the slash
> -res := gjson.Get(json, `fav\.movie`)   // no need to escape the slash 
> +// Go
> +val := gjson.Get(json, "fav\\.movie")  // must 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 
>  ```
>  
> +
>  ### Arrays
>  
>  The `#` character allows for digging into JSON Arrays.
> @@ -128,6 +135,37 @@ changed in v1.3.0 as to avoid confusion with the new [multipath](#multipaths)
>  syntax. For backwards compatibility, `#[...]` will continue to work until the
>  next major release.*
>  
> +The `~` (tilde) operator will convert a value to a boolean before comparison.
> +
> +For example, using the following JSON:
> +
> +```json
> +{
> +  "vals": [
> +    { "a": 1, "b": true },
> +    { "a": 2, "b": true },
> +    { "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 }
> +  ]
> +}
> +```
> +
> +You can now query for all true(ish) or false(ish) values:
> +
> +```
> +vals.#(b==~true)#.a    >> [1,2,6,7,8]
> +vals.#(b==~false)#.a   >> [3,4,5,9,10,11]
> +```
> +
> +The last value which was non-existent is treated as `false`
> +
>  ### Dot vs Pipe
>  
>  The `.` is standard separator, but it's also possible to use a `|`. 
> @@ -198,6 +236,11 @@ There are currently the following built-in modifiers:
>  - `@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.
> +- `@group`: Groups arrays of objects. See [e4fc67c](https://github.com/tidwall/gjson/commit/e4fc67c92aeebf2089fabc7872f010e340d105db).
>  
>  #### Modifier arguments
>  
> @@ -248,13 +291,15 @@ gjson.AddModifier("case", func(json, arg string) string {
>  "children.@case:lower.@reverse"    ["jack","alex","sara"]
>  ```
>  
> +*Note: Custom modifiers are not yet available in the Rust version*
> +
>  ### Multipaths
>  
>  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.
> +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 multipath:
>  
>  ```
>  {name.first,age,"the_murphys":friends.#(last="Murphy")#.first}
> @@ -270,8 +315,28 @@ determined, then "_" is used.
>  
>  This results in
>  
> -```
> +```json
>  {"first":"Tom","age":37,"the_murphys":["Dale","Jane"]}
>  ```
>  
> +### 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).  
> +
> +A json literal begins with the '!' declaration character. 
> +
> +For example, using the given multipath:
> +
> +```
> +{name.first,age,"company":!"Happysoft","employed":!true}
> +```
> +
> +Here we selected the first name and age. Then add two new fields, "company" and "employed".
> +
> +This results in 
> +
> +```json
> +{"first":"Tom","age":37,"company":"Happysoft","employed":true}
> +```
>  
> +*See issue [#249](https://github.com/tidwall/gjson/issues/249) for additional context on JSON Literals.*
> diff --git a/debian/changelog b/debian/changelog
> index ce04770..9b62e04 100644
> --- a/debian/changelog
> +++ b/debian/changelog
> @@ -1,3 +1,18 @@
> +golang-github-tidwall-gjson (1.14.4-2) unstable; urgency=medium
> +
> +  * Upload to unstable.
> +
> + -- Cyril Brulebois <cyril@debamax.com>  Mon, 13 Mar 2023 00:32:40 +0100
> +
> +golang-github-tidwall-gjson (1.14.4-1) experimental; urgency=medium
> +
> +  * New upstream release.
> +  * Security fixes (ReDoS – regular expression denial of service):
> +     - CVE-2021-42248 (Closes: #1011616).
> +     - CVE-2021-42836 (Closes: #1000225).
> +
> + -- Cyril Brulebois <cyril@debamax.com>  Sun, 05 Mar 2023 01:34:13 +0100
> +
>  golang-github-tidwall-gjson (1.6.7-2) unstable; urgency=medium
>  
>    [ Debian Janitor ]
> diff --git a/gjson.go b/gjson.go
> index 66965ba..53cbd23 100644
> --- a/gjson.go
> +++ b/gjson.go
> @@ -2,8 +2,6 @@
>  package gjson
>  
>  import (
> -	"encoding/json"
> -	"reflect"
>  	"strconv"
>  	"strings"
>  	"time"
> @@ -65,6 +63,9 @@ type Result struct {
>  	Num float64
>  	// 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.
> +	Indexes []int
>  }
>  
>  // String returns a string representation of the value.
> @@ -187,14 +188,15 @@ func (t Result) Time() time.Time {
>  }
>  
>  // Array returns back an array of values.
> -// 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
> +// If the result represents a null value or is non-existent, 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.
>  func (t Result) Array() []Result {
>  	if t.Type == Null {
>  		return []Result{}
>  	}
> -	if t.Type != JSON {
> +	if !t.IsArray() {
>  		return []Result{t}
>  	}
>  	r := t.arrayOrMap('[', false)
> @@ -211,6 +213,11 @@ func (t Result) IsArray() bool {
>  	return t.Type == JSON && len(t.Raw) > 0 && t.Raw[0] == '['
>  }
>  
> +// IsBool returns true if the result value is a JSON boolean.
> +func (t Result) IsBool() bool {
> +	return t.Type == True || t.Type == False
> +}
> +
>  // ForEach iterates through values.
>  // 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
> @@ -226,17 +233,19 @@ func (t Result) ForEach(iterator func(key, value Result) bool) {
>  		return
>  	}
>  	json := t.Raw
> -	var keys bool
> +	var obj bool
>  	var i int
>  	var key, value Result
>  	for ; i < len(json); i++ {
>  		if json[i] == '{' {
>  			i++
>  			key.Type = String
> -			keys = true
> +			obj = true
>  			break
>  		} else if json[i] == '[' {
>  			i++
> +			key.Type = Number
> +			key.Num = -1
>  			break
>  		}
>  		if json[i] > ' ' {
> @@ -246,8 +255,9 @@ func (t Result) ForEach(iterator func(key, value Result) bool) {
>  	var str string
>  	var vesc bool
>  	var ok bool
> +	var idx int
>  	for ; i < len(json); i++ {
> -		if keys {
> +		if obj {
>  			if json[i] != '"' {
>  				continue
>  			}
> @@ -262,7 +272,9 @@ func (t Result) ForEach(iterator func(key, value Result) bool) {
>  				key.Str = str[1 : len(str)-1]
>  			}
>  			key.Raw = str
> -			key.Index = s
> +			key.Index = s + t.Index
> +		} else {
> +			key.Num += 1
>  		}
>  		for ; i < len(json); i++ {
>  			if json[i] <= ' ' || json[i] == ',' || json[i] == ':' {
> @@ -275,14 +287,22 @@ func (t Result) ForEach(iterator func(key, value Result) bool) {
>  		if !ok {
>  			return
>  		}
> -		value.Index = s
> +		if t.Indexes != nil {
> +			if idx < len(t.Indexes) {
> +				value.Index = t.Indexes[idx]
> +			}
> +		} else {
> +			value.Index = s + t.Index
> +		}
>  		if !iterator(key, value) {
>  			return
>  		}
> +		idx++
>  	}
>  }
>  
> -// Map returns back an map of values. The result should be a JSON array.
> +// Map returns back a map of values. The result should be a JSON object.
> +// If the result is not a JSON object, the return value will be an empty map.
>  func (t Result) Map() map[string]Result {
>  	if t.Type != JSON {
>  		return map[string]Result{}
> @@ -294,7 +314,15 @@ func (t Result) Map() map[string]Result {
>  // 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)
> +	r := Get(t.Raw, path)
> +	if r.Indexes != nil {
> +		for i := 0; i < len(r.Indexes); i++ {
> +			r.Indexes[i] += t.Index
> +		}
> +	} else {
> +		r.Index += t.Index
> +	}
> +	return r
>  }
>  
>  type arrayOrMapResult struct {
> @@ -385,6 +413,8 @@ func (t Result) arrayOrMap(vc byte, valueize bool) (r arrayOrMapResult) {
>  			value.Raw, value.Str = tostr(json[i:])
>  			value.Num = 0
>  		}
> +		value.Index = i + t.Index
> +
>  		i += len(value.Raw) - 1
>  
>  		if r.vc == '{' {
> @@ -411,6 +441,17 @@ func (t Result) arrayOrMap(vc byte, valueize bool) (r arrayOrMapResult) {
>  		}
>  	}
>  end:
> +	if t.Indexes != nil {
> +		if len(t.Indexes) != len(r.a) {
> +			for i := 0; i < len(r.a); i++ {
> +				r.a[i].Index = 0
> +			}
> +		} else {
> +			for i := 0; i < len(r.a); i++ {
> +				r.a[i].Index = t.Indexes[i]
> +			}
> +		}
> +	}
>  	return
>  }
>  
> @@ -422,7 +463,8 @@ end:
>  // use the Valid function first.
>  func Parse(json string) Result {
>  	var value Result
> -	for i := 0; i < len(json); i++ {
> +	i := 0
> +	for ; i < len(json); i++ {
>  		if json[i] == '{' || json[i] == '[' {
>  			value.Type = JSON
>  			value.Raw = json[i:] // just take the entire raw
> @@ -432,16 +474,20 @@ func Parse(json string) Result {
>  			continue
>  		}
>  		switch json[i] {
> -		default:
> -			if (json[i] >= '0' && json[i] <= '9') || json[i] == '-' {
> +		case '+', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
> +			'i', 'I', 'N':
> +			value.Type = Number
> +			value.Raw, value.Num = tonum(json[i:])
> +		case 'n':
> +			if i+1 < len(json) && json[i+1] != 'u' {
> +				// nan
>  				value.Type = Number
>  				value.Raw, value.Num = tonum(json[i:])
>  			} else {
> -				return Result{}
> +				// null
> +				value.Type = Null
> +				value.Raw = tolit(json[i:])
>  			}
> -		case 'n':
> -			value.Type = Null
> -			value.Raw = tolit(json[i:])
>  		case 't':
>  			value.Type = True
>  			value.Raw = tolit(json[i:])
> @@ -451,9 +497,14 @@ func Parse(json string) Result {
>  		case '"':
>  			value.Type = String
>  			value.Raw, value.Str = tostr(json[i:])
> +		default:
> +			return Result{}
>  		}
>  		break
>  	}
> +	if value.Exists() {
> +		value.Index = i
> +	}
>  	return value
>  }
>  
> @@ -527,20 +578,12 @@ func tonum(json string) (raw string, num float64) {
>  				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
> +		} else if json[i] == ']' || json[i] == '}' {
> +			// break on ']' or '}'
> +			raw = json[:i]
> +			num, _ = strconv.ParseFloat(raw, 64)
> +			return
>  		}
> -		// likely a ']' or '}'
> -		raw = json[:i]
> -		num, _ = strconv.ParseFloat(raw, 64)
> -		return
>  	}
>  	raw = json
>  	num, _ = strconv.ParseFloat(raw, 64)
> @@ -585,7 +628,7 @@ func tostr(json string) (raw string, str string) {
>  							continue
>  						}
>  					}
> -					break
> +					return json[:i+1], unescape(json[1:i])
>  				}
>  			}
>  			var ret string
> @@ -715,10 +758,10 @@ type arrayPathResult struct {
>  	alogkey string
>  	query   struct {
>  		on    bool
> +		all   bool
>  		path  string
>  		op    string
>  		value string
> -		all   bool
>  	}
>  }
>  
> @@ -732,8 +775,13 @@ func parseArrayPath(path string) (r arrayPathResult) {
>  		}
>  		if path[i] == '.' {
>  			r.part = path[:i]
> -			r.path = path[i+1:]
> -			r.more = true
> +			if !r.arrch && i < len(path)-1 && isDotPiperChar(path[i+1:]) {
> +				r.pipe = path[i+1:]
> +				r.piped = true
> +			} else {
> +				r.path = path[i+1:]
> +				r.more = true
> +			}
>  			return
>  		}
>  		if path[i] == '#' {
> @@ -746,120 +794,27 @@ func parseArrayPath(path string) (r arrayPathResult) {
>  				} else if path[1] == '[' || path[1] == '(' {
>  					// query
>  					r.query.on = true
> -					if true {
> -						qpath, op, value, _, fi, ok := parseQuery(path[i:])
> -						if !ok {
> -							// bad query, end now
> -							break
> -						}
> -						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
> -						}
> -					} else {
> -						var end byte
> -						if path[1] == '[' {
> -							end = ']'
> -						} else {
> -							end = ')'
> -						}
> -						i += 2
> -						// whitespace
> -						for ; i < len(path); i++ {
> -							if path[i] > ' ' {
> -								break
> -							}
> -						}
> -						s := i
> -						for ; i < len(path); i++ {
> -							if path[i] <= ' ' ||
> -								path[i] == '!' ||
> -								path[i] == '=' ||
> -								path[i] == '<' ||
> -								path[i] == '>' ||
> -								path[i] == '%' ||
> -								path[i] == end {
> -								break
> -							}
> -						}
> -						r.query.path = path[s:i]
> -						// whitespace
> -						for ; i < len(path); i++ {
> -							if path[i] > ' ' {
> -								break
> -							}
> -						}
> -						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
> -												}
> -											}
> -											break
> -										}
> -									}
> -								} else if path[i] == end {
> -									if i+1 < len(path) && path[i+1] == '#' {
> -										r.query.all = true
> -									}
> -									break
> -								}
> -							}
> -							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
> +					qpath, op, value, _, fi, vesc, ok :=
> +						parseQuery(path[i:])
> +					if !ok {
> +						// bad query, end now
> +						break
> +					}
> +					if len(value) >= 2 && value[0] == '"' &&
> +						value[len(value)-1] == '"' {
> +						value = value[1 : len(value)-1]
> +						if vesc {
> +							value = unescape(value)
>  						}
>  					}
> +					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
> +					}
>  				}
>  			}
>  			continue
> @@ -885,11 +840,11 @@ func parseArrayPath(path string) (r arrayPathResult) {
>  //                              # middle
>  //   .cap                       # right
>  func parseQuery(query string) (
> -	path, op, value, remain string, i int, ok bool,
> +	path, op, value, remain string, i int, vesc, ok bool,
>  ) {
>  	if len(query) < 2 || query[0] != '#' ||
>  		(query[1] != '(' && query[1] != '[') {
> -		return "", "", "", "", i, false
> +		return "", "", "", "", i, false, false
>  	}
>  	i = 2
>  	j := 0 // start of value part
> @@ -917,6 +872,7 @@ func parseQuery(query string) (
>  			i++
>  			for ; i < len(query); i++ {
>  				if query[i] == '\\' {
> +					vesc = true
>  					i++
>  				} else if query[i] == '"' {
>  					break
> @@ -925,7 +881,7 @@ func parseQuery(query string) (
>  		}
>  	}
>  	if depth > 0 {
> -		return "", "", "", "", i, false
> +		return "", "", "", "", i, false, false
>  	}
>  	if j > 0 {
>  		path = trim(query[2:j])
> @@ -962,7 +918,7 @@ func parseQuery(query string) (
>  		path = trim(query[2:i])
>  		remain = query[i+1:]
>  	}
> -	return path, op, value, remain, i + 1, true
> +	return path, op, value, remain, i + 1, vesc, true
>  }
>  
>  func trim(s string) string {
> @@ -979,6 +935,26 @@ right:
>  	return s
>  }
>  
> +// peek at the next byte and see if it's a '@', '[', or '{'.
> +func isDotPiperChar(s string) bool {
> +	if DisableModifiers {
> +		return false
> +	}
> +	c := s[0]
> +	if c == '@' {
> +		// check that the next component is *not* a modifier.
> +		i := 1
> +		for ; i < len(s); i++ {
> +			if s[i] == '.' || s[i] == '|' || s[i] == ':' {
> +				break
> +			}
> +		}
> +		_, ok := modifiers[s[1:i]]
> +		return ok
> +	}
> +	return c == '[' || c == '{'
> +}
> +
>  type objectPathResult struct {
>  	part  string
>  	path  string
> @@ -997,12 +973,8 @@ func parseObjectPath(path string) (r objectPathResult) {
>  			return
>  		}
>  		if path[i] == '.' {
> -			// peek at the next byte and see if it's a '@', '[', or '{'.
>  			r.part = path[:i]
> -			if !DisableModifiers &&
> -				i < len(path)-1 &&
> -				(path[i+1] == '@' ||
> -					path[i+1] == '[' || path[i+1] == '{') {
> +			if i < len(path)-1 && isDotPiperChar(path[i+1:]) {
>  				r.pipe = path[i+1:]
>  				r.piped = true
>  			} else {
> @@ -1032,16 +1004,13 @@ func parseObjectPath(path string) (r objectPathResult) {
>  						continue
>  					} else if path[i] == '.' {
>  						r.part = string(epart)
> -						// peek at the next byte and see if it's a '@' modifier
> -						if !DisableModifiers &&
> -							i < len(path)-1 && path[i+1] == '@' {
> +						if i < len(path)-1 && isDotPiperChar(path[i+1:]) {
>  							r.pipe = path[i+1:]
>  							r.piped = true
>  						} else {
>  							r.path = path[i+1:]
>  							r.more = true
>  						}
> -						r.more = true
>  						return
>  					} else if path[i] == '|' {
>  						r.part = string(epart)
> @@ -1175,9 +1144,9 @@ func parseObject(c *parseContext, i int, path string) (int, bool) {
>  		}
>  		if rp.wild {
>  			if kesc {
> -				pmatch = match.Match(unescape(key), rp.part)
> +				pmatch = matchLimit(unescape(key), rp.part)
>  			} else {
> -				pmatch = match.Match(key, rp.part)
> +				pmatch = matchLimit(key, rp.part)
>  			}
>  		} else {
>  			if kesc {
> @@ -1188,6 +1157,7 @@ func parseObject(c *parseContext, i int, path string) (int, bool) {
>  		}
>  		hit = pmatch && !rp.more
>  		for ; i < len(c.json); i++ {
> +			var num bool
>  			switch c.json[i] {
>  			default:
>  				continue
> @@ -1235,15 +1205,13 @@ func parseObject(c *parseContext, i int, path string) (int, bool) {
>  						return i, true
>  					}
>  				}
> -			case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
> -				i, val = parseNumber(c.json, i)
> -				if hit {
> -					c.value.Raw = val
> -					c.value.Type = Number
> -					c.value.Num, _ = strconv.ParseFloat(val, 64)
> -					return i, true
> +			case 'n':
> +				if i+1 < len(c.json) && c.json[i+1] != 'u' {
> +					num = true
> +					break
>  				}
> -			case 't', 'f', 'n':
> +				fallthrough
> +			case 't', 'f':
>  				vc := c.json[i]
>  				i, val = parseLiteral(c.json, i)
>  				if hit {
> @@ -1256,16 +1224,43 @@ func parseObject(c *parseContext, i int, path string) (int, bool) {
>  					}
>  					return i, true
>  				}
> +			case '+', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
> +				'i', 'I', 'N':
> +				num = true
> +			}
> +			if num {
> +				i, val = parseNumber(c.json, i)
> +				if hit {
> +					c.value.Raw = val
> +					c.value.Type = Number
> +					c.value.Num, _ = strconv.ParseFloat(val, 64)
> +					return i, true
> +				}
>  			}
>  			break
>  		}
>  	}
>  	return i, false
>  }
> +
> +// matchLimit will limit the complexity of the match operation to avoid ReDos
> +// attacks from arbritary inputs.
> +// See the github.com/tidwall/match.MatchLimit function for more information.
> +func matchLimit(str, pattern string) bool {
> +	matched, _ := match.MatchLimit(str, pattern, 10000)
> +	return matched
> +}
> +
>  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]
> +	if len(rpv) > 0 && rpv[0] == '~' {
> +		// convert to bool
> +		rpv = rpv[1:]
> +		if value.Bool() {
> +			value = Result{Type: True}
> +		} else {
> +			value = Result{Type: False}
> +		}
>  	}
>  	if !value.Exists() {
>  		return false
> @@ -1293,9 +1288,9 @@ func queryMatches(rp *arrayPathResult, value Result) bool {
>  		case ">=":
>  			return value.Str >= rpv
>  		case "%":
> -			return match.Match(value.Str, rpv)
> +			return matchLimit(value.Str, rpv)
>  		case "!%":
> -			return !match.Match(value.Str, rpv)
> +			return !matchLimit(value.Str, rpv)
>  		}
>  	case Number:
>  		rpvn, _ := strconv.ParseFloat(rpv, 64)
> @@ -1345,6 +1340,7 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
>  	var alog []int
>  	var partidx int
>  	var multires []byte
> +	var queryIndexes []int
>  	rp := parseArrayPath(path)
>  	if !rp.arrch {
>  		n, ok := parseUint(rp.part)
> @@ -1365,6 +1361,10 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
>  				multires = append(multires, '[')
>  			}
>  		}
> +		var tmp parseContext
> +		tmp.value = qval
> +		fillIndex(c.json, &tmp)
> +		parentIndex := tmp.value.Index
>  		var res Result
>  		if qval.Type == JSON {
>  			res = qval.Get(rp.query.path)
> @@ -1396,6 +1396,7 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
>  						multires = append(multires, ',')
>  					}
>  					multires = append(multires, raw...)
> +					queryIndexes = append(queryIndexes, res.Index+parentIndex)
>  				}
>  			} else {
>  				c.value = res
> @@ -1404,7 +1405,6 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
>  		}
>  		return false
>  	}
> -
>  	for i < len(c.json)+1 {
>  		if !rp.arrch {
>  			pmatch = partidx == h
> @@ -1423,6 +1423,7 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
>  			} else {
>  				ch = c.json[i]
>  			}
> +			var num bool
>  			switch ch {
>  			default:
>  				continue
> @@ -1505,26 +1506,13 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
>  						return i, true
>  					}
>  				}
> -			case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
> -				i, val = parseNumber(c.json, i)
> -				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 {
> -					if rp.alogok {
> -						break
> -					}
> -					c.value.Raw = val
> -					c.value.Type = Number
> -					c.value.Num, _ = strconv.ParseFloat(val, 64)
> -					return i, true
> +			case 'n':
> +				if i+1 < len(c.json) && c.json[i+1] != 'u' {
> +					num = true
> +					break
>  				}
> -			case 't', 'f', 'n':
> +				fallthrough
> +			case 't', 'f':
>  				vc := c.json[i]
>  				i, val = parseLiteral(c.json, i)
>  				if rp.query.on {
> @@ -1552,6 +1540,9 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
>  					}
>  					return i, true
>  				}
> +			case '+', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
> +				'i', 'I', 'N':
> +				num = true
>  			case ']':
>  				if rp.arrch && rp.part == "#" {
>  					if rp.alogok {
> @@ -1561,6 +1552,7 @@ 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)
>  						jsons = append(jsons, '[')
>  						for j, k := 0, 0; j < len(alog); j++ {
> @@ -1586,6 +1578,7 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
>  											raw = res.String()
>  										}
>  										jsons = append(jsons, []byte(raw)...)
> +										indexes = append(indexes, res.Index)
>  										k++
>  									}
>  								}
> @@ -1594,6 +1587,7 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
>  						jsons = append(jsons, ']')
>  						c.value.Type = JSON
>  						c.value.Raw = string(jsons)
> +						c.value.Indexes = indexes
>  						return i + 1, true
>  					}
>  					if rp.alogok {
> @@ -1606,14 +1600,42 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
>  					c.calcd = true
>  					return i + 1, true
>  				}
> -				if len(multires) > 0 && !c.value.Exists() {
> -					c.value = Result{
> -						Raw:  string(append(multires, ']')),
> -						Type: JSON,
> +				if !c.value.Exists() {
> +					if len(multires) > 0 {
> +						c.value = Result{
> +							Raw:     string(append(multires, ']')),
> +							Type:    JSON,
> +							Indexes: queryIndexes,
> +						}
> +					} else if rp.query.all {
> +						c.value = Result{
> +							Raw:  "[]",
> +							Type: JSON,
> +						}
>  					}
>  				}
>  				return i + 1, false
>  			}
> +			if num {
> +				i, val = parseNumber(c.json, i)
> +				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 {
> +					if rp.alogok {
> +						break
> +					}
> +					c.value.Raw = val
> +					c.value.Type = Number
> +					c.value.Num, _ = strconv.ParseFloat(val, 64)
> +					return i, true
> +				}
> +			}
>  			break
>  		}
>  	}
> @@ -1729,7 +1751,7 @@ type subSelector struct {
>  // 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) {
> -	modifer := 0
> +	modifier := 0
>  	depth := 1
>  	colon := 0
>  	start := 1
> @@ -1744,6 +1766,7 @@ func parseSubSelectors(path string) (sels []subSelector, out string, ok bool) {
>  		}
>  		sels = append(sels, sel)
>  		colon = 0
> +		modifier = 0
>  		start = i + 1
>  	}
>  	for ; i < len(path); i++ {
> @@ -1751,11 +1774,11 @@ func parseSubSelectors(path string) (sels []subSelector, out string, ok bool) {
>  		case '\\':
>  			i++
>  		case '@':
> -			if modifer == 0 && i > 0 && (path[i-1] == '.' || path[i-1] == '|') {
> -				modifer = i
> +			if modifier == 0 && i > 0 && (path[i-1] == '.' || path[i-1] == '|') {
> +				modifier = i
>  			}
>  		case ':':
> -			if modifer == 0 && colon == 0 && depth == 1 {
> +			if modifier == 0 && colon == 0 && depth == 1 {
>  				colon = i
>  			}
>  		case ',':
> @@ -1808,24 +1831,71 @@ func isSimpleName(component string) bool {
>  			return false
>  		}
>  		switch component[i] {
> -		case '[', ']', '{', '}', '(', ')', '#', '|':
> +		case '[', ']', '{', '}', '(', ')', '#', '|', '!':
>  			return false
>  		}
>  	}
>  	return true
>  }
>  
> -func appendJSONString(dst []byte, s string) []byte {
> +var hexchars = [...]byte{
> +	'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
> +	'a', 'b', 'c', 'd', 'e', 'f',
> +}
> +
> +func appendHex16(dst []byte, x uint16) []byte {
> +	return append(dst,
> +		hexchars[x>>12&0xF], hexchars[x>>8&0xF],
> +		hexchars[x>>4&0xF], hexchars[x>>0&0xF],
> +	)
> +}
> +
> +// AppendJSONString is a convenience function that converts the provided string
> +// to a valid JSON string and appends it to dst.
> +func AppendJSONString(dst []byte, s string) []byte {
> +	dst = append(dst, make([]byte, len(s)+2)...)
> +	dst = append(dst[:len(dst)-len(s)-2], '"')
>  	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)...)
> +		if s[i] < ' ' {
> +			dst = append(dst, '\\')
> +			switch s[i] {
> +			case '\n':
> +				dst = append(dst, 'n')
> +			case '\r':
> +				dst = append(dst, 'r')
> +			case '\t':
> +				dst = append(dst, 't')
> +			default:
> +				dst = append(dst, 'u')
> +				dst = appendHex16(dst, uint16(s[i]))
> +			}
> +		} else if s[i] == '>' || s[i] == '<' || s[i] == '&' {
> +			dst = append(dst, '\\', 'u')
> +			dst = appendHex16(dst, uint16(s[i]))
> +		} else if s[i] == '\\' {
> +			dst = append(dst, '\\', '\\')
> +		} else if s[i] == '"' {
> +			dst = append(dst, '\\', '"')
> +		} else if s[i] > 127 {
> +			// read utf8 character
> +			r, n := utf8.DecodeRuneInString(s[i:])
> +			if n == 0 {
> +				break
> +			}
> +			if r == utf8.RuneError && n == 1 {
> +				dst = append(dst, `\ufffd`...)
> +			} else if r == '\u2028' || r == '\u2029' {
> +				dst = append(dst, `\u202`...)
> +				dst = append(dst, hexchars[r&0xF])
> +			} else {
> +				dst = append(dst, s[i:i+n]...)
> +			}
> +			i = i + n - 1
> +		} else {
> +			dst = append(dst, s[i])
>  		}
>  	}
> -	dst = append(dst, '"')
> -	dst = append(dst, s...)
> -	dst = append(dst, '"')
> -	return dst
> +	return append(dst, '"')
>  }
>  
>  type parseContext struct {
> @@ -1872,22 +1942,25 @@ type parseContext struct {
>  // use the Valid function first.
>  func Get(json, path string) Result {
>  	if len(path) > 1 {
> -		if !DisableModifiers {
> -			if path[0] == '@' {
> -				// possible modifier
> -				var ok bool
> -				var npath string
> -				var rjson string
> +		if (path[0] == '@' && !DisableModifiers) || path[0] == '!' {
> +			// possible modifier
> +			var ok bool
> +			var npath string
> +			var rjson string
> +			if path[0] == '@' && !DisableModifiers {
>  				npath, rjson, ok = execModifier(json, path)
> -				if ok {
> -					path = npath
> -					if len(path) > 0 && (path[0] == '|' || path[0] == '.') {
> -						res := Get(rjson, path[1:])
> -						res.Index = 0
> -						return res
> -					}
> -					return Parse(rjson)
> +			} else if path[0] == '!' {
> +				npath, rjson, ok = execStatic(json, path)
> +			}
> +			if ok {
> +				path = npath
> +				if len(path) > 0 && (path[0] == '|' || path[0] == '.') {
> +					res := Get(rjson, path[1:])
> +					res.Index = 0
> +					res.Indexes = nil
> +					return res
>  				}
> +				return Parse(rjson)
>  			}
>  		}
>  		if path[0] == '[' || path[0] == '{' {
> @@ -1912,14 +1985,14 @@ func Get(json, path string) Result {
>  									if sub.name[0] == '"' && Valid(sub.name) {
>  										b = append(b, sub.name...)
>  									} else {
> -										b = appendJSONString(b, sub.name)
> +										b = AppendJSONString(b, sub.name)
>  									}
>  								} else {
>  									last := nameOfLast(sub.path)
>  									if isSimpleName(last) {
> -										b = appendJSONString(b, last)
> +										b = AppendJSONString(b, last)
>  									} else {
> -										b = appendJSONString(b, "_")
> +										b = AppendJSONString(b, "_")
>  									}
>  								}
>  								b = append(b, ':')
> @@ -2124,11 +2197,15 @@ func parseAny(json string, i int, hit bool) (int, Result, bool) {
>  				res.Raw = val
>  				res.Type = JSON
>  			}
> -			return i, res, true
> +			var tmp parseContext
> +			tmp.value = res
> +			fillIndex(json, &tmp)
> +			return i, tmp.value, true
>  		}
>  		if json[i] <= ' ' {
>  			continue
>  		}
> +		var num bool
>  		switch json[i] {
>  		case '"':
>  			i++
> @@ -2148,15 +2225,13 @@ func parseAny(json string, i int, hit bool) (int, Result, bool) {
>  				}
>  			}
>  			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)
> +		case 'n':
> +			if i+1 < len(json) && json[i+1] != 'u' {
> +				num = true
> +				break
>  			}
> -			return i, res, true
> -		case 't', 'f', 'n':
> +			fallthrough
> +		case 't', 'f':
>  			vc := json[i]
>  			i, val = parseLiteral(json, i)
>  			if hit {
> @@ -2169,16 +2244,24 @@ func parseAny(json string, i int, hit bool) (int, Result, bool) {
>  				}
>  				return i, res, true
>  			}
> +		case '+', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
> +			'i', 'I', 'N':
> +			num = true
>  		}
> +		if num {
> +			i, val = parseNumber(json, i)
> +			if hit {
> +				res.Raw = val
> +				res.Type = Number
> +				res.Num, _ = strconv.ParseFloat(val, 64)
> +			}
> +			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.
> @@ -2379,6 +2462,12 @@ func validnumber(data []byte, i int) (outi int, ok bool) {
>  	// sign
>  	if data[i] == '-' {
>  		i++
> +		if i == len(data) {
> +			return i, false
> +		}
> +		if data[i] < '0' || data[i] > '9' {
> +			return i, false
> +		}
>  	}
>  	// int
>  	if i == len(data) {
> @@ -2529,15 +2618,51 @@ func parseInt(s string) (n int64, ok bool) {
>  	return n, true
>  }
>  
> +// safeInt validates a given JSON number
> +// ensures it lies within the minimum and maximum representable JSON numbers
>  func safeInt(f float64) (n int64, ok bool) {
> +	// https://tc39.es/ecma262/#sec-number.min_safe_integer
> +	// https://tc39.es/ecma262/#sec-number.max_safe_integer
>  	if f < -9007199254740991 || f > 9007199254740991 {
>  		return 0, false
>  	}
>  	return int64(f), true
>  }
>  
> +// execStatic parses the path to find a static value.
> +// The input expects that the path already starts with a '!'
> +func execStatic(json, path string) (pathOut, res string, ok bool) {
> +	name := path[1:]
> +	if len(name) > 0 {
> +		switch name[0] {
> +		case '{', '[', '"', '+', '-', '0', '1', '2', '3', '4', '5', '6', '7',
> +			'8', '9':
> +			_, res = parseSquash(name, 0)
> +			pathOut = name[len(res):]
> +			return pathOut, res, true
> +		}
> +	}
> +	for i := 1; i < len(path); i++ {
> +		if path[i] == '|' {
> +			pathOut = path[i:]
> +			name = path[1:i]
> +			break
> +		}
> +		if path[i] == '.' {
> +			pathOut = path[i:]
> +			name = path[1:i]
> +			break
> +		}
> +	}
> +	switch strings.ToLower(name) {
> +	case "true", "false", "null", "nan", "inf":
> +		return pathOut, name, true
> +	}
> +	return pathOut, res, false
> +}
> +
>  // execModifier parses the path to find a matching modifier function.
> -// then input expects that the path already starts with a '@'
> +// The 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
> @@ -2608,6 +2733,11 @@ var modifiers = map[string]func(json, arg string) string{
>  	"flatten": modFlatten,
>  	"join":    modJoin,
>  	"valid":   modValid,
> +	"keys":    modKeys,
> +	"values":  modValues,
> +	"tostr":   modToStr,
> +	"fromstr": modFromStr,
> +	"group":   modGroup,
>  }
>  
>  // AddModifier binds a custom modifier command to the GJSON syntax.
> @@ -2740,25 +2870,82 @@ func modFlatten(json, arg string) string {
>  	out = append(out, '[')
>  	var idx int
>  	res.ForEach(func(_, value Result) bool {
> -		if idx > 0 {
> -			out = append(out, ',')
> -		}
> +		var raw string
>  		if value.IsArray() {
>  			if deep {
> -				out = append(out, unwrap(modFlatten(value.Raw, arg))...)
> +				raw = unwrap(modFlatten(value.Raw, arg))
>  			} else {
> -				out = append(out, unwrap(value.Raw)...)
> +				raw = unwrap(value.Raw)
>  			}
>  		} else {
> -			out = append(out, value.Raw...)
> +			raw = value.Raw
> +		}
> +		raw = strings.TrimSpace(raw)
> +		if len(raw) > 0 {
> +			if idx > 0 {
> +				out = append(out, ',')
> +			}
> +			out = append(out, raw...)
> +			idx++
>  		}
> -		idx++
>  		return true
>  	})
>  	out = append(out, ']')
>  	return bytesString(out)
>  }
>  
> +// @keys extracts the keys from an object.
> +//  {"first":"Tom","last":"Smith"} -> ["first","last"]
> +func modKeys(json, arg string) string {
> +	v := Parse(json)
> +	if !v.Exists() {
> +		return "[]"
> +	}
> +	obj := v.IsObject()
> +	var out strings.Builder
> +	out.WriteByte('[')
> +	var i int
> +	v.ForEach(func(key, _ Result) bool {
> +		if i > 0 {
> +			out.WriteByte(',')
> +		}
> +		if obj {
> +			out.WriteString(key.Raw)
> +		} else {
> +			out.WriteString("null")
> +		}
> +		i++
> +		return true
> +	})
> +	out.WriteByte(']')
> +	return out.String()
> +}
> +
> +// @values extracts the values from an object.
> +//   {"first":"Tom","last":"Smith"} -> ["Tom","Smith"]
> +func modValues(json, arg string) string {
> +	v := Parse(json)
> +	if !v.Exists() {
> +		return "[]"
> +	}
> +	if v.IsArray() {
> +		return json
> +	}
> +	var out strings.Builder
> +	out.WriteByte('[')
> +	var i int
> +	v.ForEach(func(_, value Result) bool {
> +		if i > 0 {
> +			out.WriteByte(',')
> +		}
> +		out.WriteString(value.Raw)
> +		i++
> +		return true
> +	})
> +	out.WriteByte(']')
> +	return out.String()
> +}
> +
>  // @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.
> @@ -2836,6 +3023,69 @@ func modValid(json, arg string) string {
>  	return json
>  }
>  
> +// @fromstr converts a string to json
> +//   "{\"id\":1023,\"name\":\"alert\"}" -> {"id":1023,"name":"alert"}
> +func modFromStr(json, arg string) string {
> +	if !Valid(json) {
> +		return ""
> +	}
> +	return Parse(json).String()
> +}
> +
> +// @tostr converts a string to json
> +//   {"id":1023,"name":"alert"} -> "{\"id\":1023,\"name\":\"alert\"}"
> +func modToStr(str, arg string) string {
> +	return string(AppendJSONString(nil, str))
> +}
> +
> +func modGroup(json, arg string) string {
> +	res := Parse(json)
> +	if !res.IsObject() {
> +		return ""
> +	}
> +	var all [][]byte
> +	res.ForEach(func(key, value Result) bool {
> +		if !value.IsArray() {
> +			return true
> +		}
> +		var idx int
> +		value.ForEach(func(_, value Result) bool {
> +			if idx == len(all) {
> +				all = append(all, []byte{})
> +			}
> +			all[idx] = append(all[idx], ("," + key.Raw + ":" + value.Raw)...)
> +			idx++
> +			return true
> +		})
> +		return true
> +	})
> +	var data []byte
> +	data = append(data, '[')
> +	for i, item := range all {
> +		if i > 0 {
> +			data = append(data, ',')
> +		}
> +		data = append(data, '{')
> +		data = append(data, item[1:]...)
> +		data = append(data, '}')
> +	}
> +	data = append(data, ']')
> +	return string(data)
> +}
> +
> +// stringHeader instead of reflect.StringHeader
> +type stringHeader struct {
> +	data unsafe.Pointer
> +	len  int
> +}
> +
> +// sliceHeader instead of reflect.SliceHeader
> +type sliceHeader struct {
> +	data unsafe.Pointer
> +	len  int
> +	cap  int
> +}
> +
>  // 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.
> @@ -2845,14 +3095,14 @@ func getBytes(json []byte, path string) Result {
>  		// 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))
> +		rawhi := *(*stringHeader)(unsafe.Pointer(&result.Raw))
> +		strhi := *(*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 {
> +		rawh := sliceHeader{data: rawhi.data, len: rawhi.len, cap: rawhi.len}
> +		strh := sliceHeader{data: strhi.data, len: strhi.len, cap: rawhi.len}
> +		if strh.data == nil {
>  			// str is nil
> -			if rawh.Data == 0 {
> +			if rawh.data == nil {
>  				// raw is nil
>  				result.Raw = ""
>  			} else {
> @@ -2860,19 +3110,20 @@ func getBytes(json []byte, path string) Result {
>  				result.Raw = string(*(*[]byte)(unsafe.Pointer(&rawh)))
>  			}
>  			result.Str = ""
> -		} else if rawh.Data == 0 {
> +		} else if rawh.data == nil {
>  			// 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 {
> +		} 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.
> -			start := int(strh.Data - rawh.Data)
> +			start := uintptr(strh.data) - uintptr(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]
> +			result.Str = result.Raw[start : start+uintptr(strh.len)]
>  		} else {
>  			// safely copy both the raw and str slice headers to strings
>  			result.Raw = string(*(*[]byte)(unsafe.Pointer(&rawh)))
> @@ -2887,9 +3138,9 @@ func getBytes(json []byte, path string) Result {
>  // 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)
> +		jhdr := *(*stringHeader)(unsafe.Pointer(&json))
> +		rhdr := *(*stringHeader)(unsafe.Pointer(&(c.value.Raw)))
> +		c.value.Index = int(uintptr(rhdr.data) - uintptr(jhdr.data))
>  		if c.value.Index < 0 || c.value.Index >= len(json) {
>  			c.value.Index = 0
>  		}
> @@ -2897,13 +3148,212 @@ func fillIndex(json string, c *parseContext) {
>  }
>  
>  func stringBytes(s string) []byte {
> -	return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
> -		Data: (*reflect.StringHeader)(unsafe.Pointer(&s)).Data,
> -		Len:  len(s),
> -		Cap:  len(s),
> +	return *(*[]byte)(unsafe.Pointer(&sliceHeader{
> +		data: (*stringHeader)(unsafe.Pointer(&s)).data,
> +		len:  len(s),
> +		cap:  len(s),
>  	}))
>  }
>  
>  func bytesString(b []byte) string {
>  	return *(*string)(unsafe.Pointer(&b))
>  }
> +
> +func revSquash(json string) string {
> +	// reverse squash
> +	// expects that the tail character is a ']' or '}' or ')' or '"'
> +	// squash the value, ignoring all nested arrays and objects.
> +	i := len(json) - 1
> +	var depth int
> +	if json[i] != '"' {
> +		depth++
> +	}
> +	if json[i] == '}' || json[i] == ']' || json[i] == ')' {
> +		i--
> +	}
> +	for ; i >= 0; i-- {
> +		switch json[i] {
> +		case '"':
> +			i--
> +			for ; i >= 0; i-- {
> +				if json[i] == '"' {
> +					esc := 0
> +					for i > 0 && json[i-1] == '\\' {
> +						i--
> +						esc++
> +					}
> +					if esc%2 == 1 {
> +						continue
> +					}
> +					i += esc
> +					break
> +				}
> +			}
> +			if depth == 0 {
> +				if i < 0 {
> +					i = 0
> +				}
> +				return json[i:]
> +			}
> +		case '}', ']', ')':
> +			depth++
> +		case '{', '[', '(':
> +			depth--
> +			if depth == 0 {
> +				return json[i:]
> +			}
> +		}
> +	}
> +	return json
> +}
> +
> +// Paths returns the original GJSON paths for a Result where the Result came
> +// from a simple query path that returns an array, like:
> +//
> +//    gjson.Get(json, "friends.#.first")
> +//
> +// The returned value will be in the form of a JSON array:
> +//
> +//    ["friends.0.first","friends.1.first","friends.2.first"]
> +//
> +// The param '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,
> +// or a nested query.
> +func (t Result) Paths(json string) []string {
> +	if t.Indexes == nil {
> +		return nil
> +	}
> +	paths := make([]string, 0, len(t.Indexes))
> +	t.ForEach(func(_, value Result) bool {
> +		paths = append(paths, value.Path(json))
> +		return true
> +	})
> +	if len(paths) != len(t.Indexes) {
> +		return nil
> +	}
> +	return paths
> +}
> +
> +// Path returns the original GJSON path for a Result where the Result came
> +// from a simple path that returns a single value, like:
> +//
> +//    gjson.Get(json, "friends.#(last=Murphy)")
> +//
> +// The returned value will be in the form of a JSON string:
> +//
> +//    "friends.0"
> +//
> +// The param '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,
> +// or a nested query.
> +func (t Result) Path(json string) string {
> +	var path []byte
> +	var comps []string // raw components
> +	i := t.Index - 1
> +	if t.Index+len(t.Raw) > len(json) {
> +		// JSON cannot safely contain Result.
> +		goto fail
> +	}
> +	if !strings.HasPrefix(json[t.Index:], t.Raw) {
> +		// Result is not at the JSON index as exepcted.
> +		goto fail
> +	}
> +	for ; i >= 0; i-- {
> +		if json[i] <= ' ' {
> +			continue
> +		}
> +		if json[i] == ':' {
> +			// inside of object, get the key
> +			for ; i >= 0; i-- {
> +				if json[i] != '"' {
> +					continue
> +				}
> +				break
> +			}
> +			raw := revSquash(json[:i+1])
> +			i = i - len(raw)
> +			comps = append(comps, raw)
> +			// key gotten, now squash the rest
> +			raw = revSquash(json[:i+1])
> +			i = i - len(raw)
> +			i++ // increment the index for next loop step
> +		} else if json[i] == '{' {
> +			// Encountered an open object. The original result was probably an
> +			// object key.
> +			goto fail
> +		} else if json[i] == ',' || json[i] == '[' {
> +			// inside of an array, count the position
> +			var arrIdx int
> +			if json[i] == ',' {
> +				arrIdx++
> +				i--
> +			}
> +			for ; i >= 0; i-- {
> +				if json[i] == ':' {
> +					// Encountered an unexpected colon. The original result was
> +					// probably an object key.
> +					goto fail
> +				} else if json[i] == ',' {
> +					arrIdx++
> +				} else if json[i] == '[' {
> +					comps = append(comps, strconv.Itoa(arrIdx))
> +					break
> +				} else if json[i] == ']' || json[i] == '}' || json[i] == '"' {
> +					raw := revSquash(json[:i+1])
> +					i = i - len(raw) + 1
> +				}
> +			}
> +		}
> +	}
> +	if len(comps) == 0 {
> +		if DisableModifiers {
> +			goto fail
> +		}
> +		return "@this"
> +	}
> +	for i := len(comps) - 1; i >= 0; i-- {
> +		rcomp := Parse(comps[i])
> +		if !rcomp.Exists() {
> +			goto fail
> +		}
> +		comp := escapeComp(rcomp.String())
> +		path = append(path, '.')
> +		path = append(path, comp...)
> +	}
> +	if len(path) > 0 {
> +		path = path[1:]
> +	}
> +	return string(path)
> +fail:
> +	return ""
> +}
> +
> +// isSafePathKeyChar returns true if the input character is safe for not
> +// needing escaping.
> +func isSafePathKeyChar(c byte) bool {
> +	return c <= ' ' || c > '~' || c == '_' || c == '-' || c == ':' ||
> +		(c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
> +		(c >= '0' && c <= '9')
> +}
> +
> +// escapeComp escaped a path compontent, making it safe for generating a
> +// path for later use.
> +func escapeComp(comp string) string {
> +	for i := 0; i < len(comp); i++ {
> +		if !isSafePathKeyChar(comp[i]) {
> +			ncomp := []byte(comp[:i])
> +			for ; i < len(comp); i++ {
> +				if !isSafePathKeyChar(comp[i]) {
> +					ncomp = append(ncomp, '\\')
> +				}
> +				ncomp = append(ncomp, comp[i])
> +			}
> +			return string(ncomp)
> +		}
> +	}
> +	return comp
> +}
> diff --git a/gjson_test.go b/gjson_test.go
> index e3bfd84..77f6ce9 100644
> --- a/gjson_test.go
> +++ b/gjson_test.go
> @@ -5,6 +5,7 @@ import (
>  	"encoding/hex"
>  	"encoding/json"
>  	"fmt"
> +	"math"
>  	"math/rand"
>  	"strconv"
>  	"strings"
> @@ -107,7 +108,7 @@ func TestEscapePath(t *testing.T) {
>  }
>  
>  // this json block is poorly formed on purpose.
> -var basicJSON = `{"age":100, "name":{"here":"B\\\"R"},
> +var basicJSON = `  {"age":100, "name":{"here":"B\\\"R"},
>  	"noop":{"what is a wren?":"a bird"},
>  	"happy":true,"immortal":false,
>  	"items":[1,2,3,{"tags":[1,2,3],"points":[[1,2],[3,4]]},4,5,6,7],
> @@ -140,9 +141,66 @@ var basicJSON = `{"age":100, "name":{"here":"B\\\"R"},
>  			}
>      	]
>  	},
> -	"lastly":{"yay":"final"}
> +	"lastly":{"end...ing":"soon","yay":"final"}
>  }`
> -var basicJSONB = []byte(basicJSON)
> +
> +func TestPath(t *testing.T) {
> +	json := basicJSON
> +	r := Get(json, "@this")
> +	path := r.Path(json)
> +	if path != "@this" {
> +		t.FailNow()
> +	}
> +
> +	r = Parse(json)
> +	path = r.Path(json)
> +	if path != "@this" {
> +		t.FailNow()
> +	}
> +
> +	obj := Parse(json)
> +	obj.ForEach(func(key, val Result) bool {
> +		kp := key.Path(json)
> +		assert(t, kp == "")
> +		vp := val.Path(json)
> +		if vp == "name" {
> +			// there are two "name" keys
> +			return true
> +		}
> +		val2 := obj.Get(vp)
> +		assert(t, val2.Raw == val.Raw)
> +		return true
> +	})
> +	arr := obj.Get("loggy.programmers")
> +	arr.ForEach(func(_, val Result) bool {
> +		vp := val.Path(json)
> +		val2 := Get(json, vp)
> +		assert(t, val2.Raw == val.Raw)
> +		return true
> +	})
> +	get := func(path string) {
> +		r1 := Get(json, path)
> +		path2 := r1.Path(json)
> +		r2 := Get(json, path2)
> +		assert(t, r1.Raw == r2.Raw)
> +	}
> +	get("age")
> +	get("name")
> +	get("name.here")
> +	get("noop")
> +	get("noop.what is a wren?")
> +	get("arr.0")
> +	get("arr.1")
> +	get("arr.2")
> +	get("arr.3")
> +	get("arr.3.hello")
> +	get("arr.4")
> +	get("arr.5")
> +	get("loggy.programmers.2.email")
> +	get("lastly.end\\.\\.\\.ing")
> +	get("lastly.yay")
> +
> +}
>  
>  func TestTimeResult(t *testing.T) {
>  	assert(t, Get(basicJSON, "created").String() ==
> @@ -164,14 +222,11 @@ func TestManyVariousPathCounts(t *testing.T) {
>  	expects := []string{"a", "b", "c"}
>  	for _, count := range counts {
>  		var gpaths []string
> -		var gexpects []string
>  		for i := 0; i < count; i++ {
>  			if i < len(paths) {
>  				gpaths = append(gpaths, paths[i])
> -				gexpects = append(gexpects, expects[i])
>  			} else {
>  				gpaths = append(gpaths, fmt.Sprintf("not%d", i))
> -				gexpects = append(gexpects, "null")
>  			}
>  		}
>  		results := GetMany(json, gpaths...)
> @@ -380,9 +435,9 @@ func TestBasic1(t *testing.T) {
>  	mtok := get(basicJSON, `loggy.programmers`)
>  	var count int
>  	mtok.ForEach(func(key, value Result) bool {
> -		if key.Exists() {
> -			t.Fatalf("expected %v, got %v", false, key.Exists())
> -		}
> +		assert(t, key.Exists())
> +		assert(t, key.String() == fmt.Sprint(count))
> +		assert(t, key.Int() == int64(count))
>  		count++
>  		if count == 3 {
>  			return false
> @@ -720,10 +775,6 @@ var exampleJSON = `{
>  	}
>  }`
>  
> -func TestNewParse(t *testing.T) {
> -	//fmt.Printf("%v\n", parse2(exampleJSON, "widget").String())
> -}
> -
>  func TestUnmarshalMap(t *testing.T) {
>  	var m1 = Parse(exampleJSON).Value().(map[string]interface{})
>  	var m2 map[string]interface{}
> @@ -738,7 +789,7 @@ func TestUnmarshalMap(t *testing.T) {
>  	if err != nil {
>  		t.Fatal(err)
>  	}
> -	if bytes.Compare(b1, b2) != 0 {
> +	if !bytes.Equal(b1, b2) {
>  		t.Fatal("b1 != b2")
>  	}
>  }
> @@ -784,9 +835,8 @@ var manyJSON = `  {
>  	"name.first":"Cat",
>  }`
>  
> -func combine(results []Result) string {
> -	return fmt.Sprintf("%v", results)
> -}
> +var testWatchForFallback bool
> +
>  func TestManyBasic(t *testing.T) {
>  	testWatchForFallback = true
>  	defer func() {
> @@ -804,9 +854,6 @@ func TestManyBasic(t *testing.T) {
>  			fmt.Printf("%v\n", paths)
>  			t.Fatalf("expected %v, got %v", expect, results)
>  		}
> -		//if testLastWasFallback != shouldFallback {
> -		//	t.Fatalf("expected %v, got %v", shouldFallback, testLastWasFallback)
> -		//}
>  	}
>  	testMany(false, "[Point]", "position.type")
>  	testMany(false, `[emptya ["world peace"] 31]`, ".a", "loves", "age")
> @@ -867,9 +914,9 @@ func TestIssue20(t *testing.T) {
>  }
>  
>  func TestIssue21(t *testing.T) {
> -	json := `{ "Level1Field1":3, 
> -	           "Level1Field4":4, 
> -			   "Level1Field2":{ "Level2Field1":[ "value1", "value2" ], 
> +	json := `{ "Level1Field1":3,
> +	           "Level1Field4":4,
> +			   "Level1Field2":{ "Level2Field1":[ "value1", "value2" ],
>  			   "Level2Field2":{ "Level3Field1":[ { "key1":"value1" } ] } } }`
>  	paths := []string{"Level1Field1", "Level1Field2.Level2Field1",
>  		"Level1Field2.Level2Field2.Level3Field1", "Level1Field4"}
> @@ -917,49 +964,6 @@ func TestRandomMany(t *testing.T) {
>  	}
>  }
>  
> -type ComplicatedType struct {
> -	unsettable int
> -	Tagged     string `json:"tagged"`
> -	NotTagged  bool
> -	Nested     struct {
> -		Yellow string `json:"yellow"`
> -	}
> -	NestedTagged struct {
> -		Green string
> -		Map   map[string]interface{}
> -		Ints  struct {
> -			Int   int `json:"int"`
> -			Int8  int8
> -			Int16 int16
> -			Int32 int32
> -			Int64 int64 `json:"int64"`
> -		}
> -		Uints struct {
> -			Uint   uint
> -			Uint8  uint8
> -			Uint16 uint16
> -			Uint32 uint32
> -			Uint64 uint64
> -		}
> -		Floats struct {
> -			Float64 float64
> -			Float32 float32
> -		}
> -		Byte byte
> -		Bool bool
> -	} `json:"nestedTagged"`
> -	LeftOut      string `json:"-"`
> -	SelfPtr      *ComplicatedType
> -	SelfSlice    []ComplicatedType
> -	SelfSlicePtr []*ComplicatedType
> -	SelfPtrSlice *[]ComplicatedType
> -	Interface    interface{} `json:"interface"`
> -	Array        [3]int
> -	Time         time.Time `json:"time"`
> -	Binary       []byte
> -	NonBinary    []byte
> -}
> -
>  var complicatedJSON = `
>  {
>  	"tagged": "OK",
> @@ -973,7 +977,7 @@ var complicatedJSON = `
>  	"nestedTagged": {
>  		"Green": "Green",
>  		"Map": {
> -			"this": "that", 
> +			"this": "that",
>  			"and": "the other thing"
>  		},
>  		"Ints": {
> @@ -1024,6 +1028,7 @@ func TestValidBasic(t *testing.T) {
>  	testvalid(t, "00", false)
>  	testvalid(t, "-00", false)
>  	testvalid(t, "-.", false)
> +	testvalid(t, "-.123", false)
>  	testvalid(t, "0.0", true)
>  	testvalid(t, "10.0", true)
>  	testvalid(t, "10e1", true)
> @@ -1088,6 +1093,8 @@ func TestValidBasic(t *testing.T) {
>  	testvalid(t, `"a\\b\\\uFFA"`, false)
>  	testvalid(t, string(complicatedJSON), true)
>  	testvalid(t, string(exampleJSON), true)
> +	testvalid(t, "[-]", false)
> +	testvalid(t, "[-.123]", false)
>  }
>  
>  var jsonchars = []string{"{", "[", ",", ":", "}", "]", "1", "0", "true",
> @@ -1180,50 +1187,6 @@ func TestNullArray(t *testing.T) {
>  	}
>  }
>  
> -// func TestRandomGetMany(t *testing.T) {
> -// 	start := time.Now()
> -// 	for time.Since(start) < time.Second*3 {
> -// 		testRandomGetMany(t)
> -// 	}
> -// }
> -func testRandomGetMany(t *testing.T) {
> -	rand.Seed(time.Now().UnixNano())
> -	json, keys := randomJSON()
> -	for _, key := range keys {
> -		r := Get(json, key)
> -		if !r.Exists() {
> -			t.Fatal("should exist")
> -		}
> -	}
> -	rkeysi := rand.Perm(len(keys))
> -	rkeysn := 1 + rand.Int()%32
> -	if len(rkeysi) > rkeysn {
> -		rkeysi = rkeysi[:rkeysn]
> -	}
> -	var rkeys []string
> -	for i := 0; i < len(rkeysi); i++ {
> -		rkeys = append(rkeys, keys[rkeysi[i]])
> -	}
> -	mres1 := GetMany(json, rkeys...)
> -	var mres2 []Result
> -	for _, rkey := range rkeys {
> -		mres2 = append(mres2, Get(json, rkey))
> -	}
> -	if len(mres1) != len(mres2) {
> -		t.Fatalf("expected %d, got %d", len(mres2), len(mres1))
> -	}
> -	for i := 0; i < len(mres1); i++ {
> -		mres1[i].Index = 0
> -		mres2[i].Index = 0
> -		v1 := fmt.Sprintf("%#v", mres1[i])
> -		v2 := fmt.Sprintf("%#v", mres2[i])
> -		if v1 != v2 {
> -			t.Fatalf("\nexpected %s\n"+
> -				"     got %s", v2, v1)
> -		}
> -	}
> -}
> -
>  func TestIssue54(t *testing.T) {
>  	var r []Result
>  	json := `{"MarketName":null,"Nounce":6115}`
> @@ -1244,93 +1207,6 @@ func TestIssue54(t *testing.T) {
>  	}
>  }
>  
> -func randomString() string {
> -	var key string
> -	N := 1 + rand.Int()%16
> -	for i := 0; i < N; i++ {
> -		r := rand.Int() % 62
> -		if r < 10 {
> -			key += string(byte('0' + r))
> -		} else if r-10 < 26 {
> -			key += string(byte('a' + r - 10))
> -		} else {
> -			key += string(byte('A' + r - 10 - 26))
> -		}
> -	}
> -	return `"` + key + `"`
> -}
> -func randomBool() string {
> -	switch rand.Int() % 2 {
> -	default:
> -		return "false"
> -	case 1:
> -		return "true"
> -	}
> -}
> -func randomNumber() string {
> -	return strconv.FormatInt(int64(rand.Int()%1000000), 10)
> -}
> -
> -func randomObjectOrArray(keys []string, prefix string, array bool, depth int) (
> -	string, []string) {
> -	N := 5 + rand.Int()%5
> -	var json string
> -	if array {
> -		json = "["
> -	} else {
> -		json = "{"
> -	}
> -	for i := 0; i < N; i++ {
> -		if i > 0 {
> -			json += ","
> -		}
> -		var pkey string
> -		if array {
> -			pkey = prefix + "." + strconv.FormatInt(int64(i), 10)
> -		} else {
> -			key := randomString()
> -			pkey = prefix + "." + key[1:len(key)-1]
> -			json += key + `:`
> -		}
> -		keys = append(keys, pkey[1:])
> -		var kind int
> -		if depth == 5 {
> -			kind = rand.Int() % 4
> -		} else {
> -			kind = rand.Int() % 6
> -		}
> -		switch kind {
> -		case 0:
> -			json += randomString()
> -		case 1:
> -			json += randomBool()
> -		case 2:
> -			json += "null"
> -		case 3:
> -			json += randomNumber()
> -		case 4:
> -			var njson string
> -			njson, keys = randomObjectOrArray(keys, pkey, true, depth+1)
> -			json += njson
> -		case 5:
> -			var njson string
> -			njson, keys = randomObjectOrArray(keys, pkey, false, depth+1)
> -			json += njson
> -		}
> -
> -	}
> -	if array {
> -		json += "]"
> -	} else {
> -		json += "}"
> -	}
> -	return json, keys
> -}
> -
> -func randomJSON() (json string, keys []string) {
> -	return randomObjectOrArray(nil, "", false, 0)
> -}
> -
>  func TestIssue55(t *testing.T) {
>  	json := `{"one": {"two": 2, "three": 3}, "four": 4, "five": 5}`
>  	results := GetMany(json, "four", "five", "one.two", "one.six")
> @@ -1444,7 +1320,7 @@ func TestNumFloatString(t *testing.T) {
>  }
>  
>  func TestDuplicateKeys(t *testing.T) {
> -	// this is vaild json according to the JSON spec
> +	// this is valid json according to the JSON spec
>  	var json = `{"name": "Alex","name": "Peter"}`
>  	if Parse(json).Get("name").String() !=
>  		Parse(json).Map()["name"].String() {
> @@ -1470,10 +1346,10 @@ func TestArrayValues(t *testing.T) {
>  	}
>  	expect := strings.Join([]string{
>  		`gjson.Result{Type:3, Raw:"\"PERSON1\"", Str:"PERSON1", Num:0, ` +
> -			`Index:0}`,
> +			`Index:11, Indexes:[]int(nil)}`,
>  		`gjson.Result{Type:3, Raw:"\"PERSON2\"", Str:"PERSON2", Num:0, ` +
> -			`Index:0}`,
> -		`gjson.Result{Type:2, Raw:"0", Str:"", Num:0, Index:0}`,
> +			`Index:21, Indexes:[]int(nil)}`,
> +		`gjson.Result{Type:2, Raw:"0", Str:"", Num:0, Index:31, Indexes:[]int(nil)}`,
>  	}, "\n")
>  	if output != expect {
>  		t.Fatalf("expected '%v', got '%v'", expect, output)
> @@ -1671,7 +1547,7 @@ func TestDeepSelectors(t *testing.T) {
>  					}
>  				},
>  				{
> -					"first": "Roger", "last": "Craig", 
> +					"first": "Roger", "last": "Craig",
>  					"extra": [40,50,60],
>  					"details": {
>  						"city": "Phoenix",
> @@ -1921,7 +1797,7 @@ func TestParseQuery(t *testing.T) {
>  	var path, op, value, remain string
>  	var ok bool
>  
> -	path, op, value, remain, _, ok =
> +	path, op, value, remain, _, _, ok =
>  		parseQuery(`#(service_roles.#(=="one").()==asdf).cap`)
>  	assert(t, ok &&
>  		path == `service_roles.#(=="one").()` &&
> @@ -1929,28 +1805,28 @@ func TestParseQuery(t *testing.T) {
>  		value == `asdf` &&
>  		remain == `.cap`)
>  
> -	path, op, value, remain, _, ok = parseQuery(`#(first_name%"Murphy").last`)
> +	path, op, value, remain, _, _, ok = parseQuery(`#(first_name%"Murphy").last`)
>  	assert(t, ok &&
>  		path == `first_name` &&
>  		op == `%` &&
>  		value == `"Murphy"` &&
>  		remain == `.last`)
>  
> -	path, op, value, remain, _, ok = parseQuery(`#( first_name !% "Murphy" ).last`)
> +	path, op, value, remain, _, _, ok = parseQuery(`#( first_name !% "Murphy" ).last`)
>  	assert(t, ok &&
>  		path == `first_name` &&
>  		op == `!%` &&
>  		value == `"Murphy"` &&
>  		remain == `.last`)
>  
> -	path, op, value, remain, _, ok = parseQuery(`#(service_roles.#(=="one"))`)
> +	path, op, value, remain, _, _, ok = parseQuery(`#(service_roles.#(=="one"))`)
>  	assert(t, ok &&
>  		path == `service_roles.#(=="one")` &&
>  		op == `` &&
>  		value == `` &&
>  		remain == ``)
>  
> -	path, op, value, remain, _, ok =
> +	path, op, value, remain, _, _, ok =
>  		parseQuery(`#(a\("\"(".#(=="o\"(ne")%"ab\")").remain`)
>  	assert(t, ok &&
>  		path == `a\("\"(".#(=="o\"(ne")` &&
> @@ -2016,6 +1892,9 @@ func TestModifiersInMultipaths(t *testing.T) {
>  	exp = `{"first":"DALE"}`
>  	assert(t, res.Raw == exp)
>  
> +	res = Get(readmeJSON, `{"children":children|@case:upper,"name":name.first,"age":age}`)
> +	exp = `{"children":["SARA","ALEX","JACK"],"name":"Tom","age":37}`
> +	assert(t, res.Raw == exp)
>  }
>  
>  func TestIssue141(t *testing.T) {
> @@ -2203,4 +2082,512 @@ func TestVariousFuzz(t *testing.T) {
>  	testJSON = `[#.@pretty.@join:{""[]""preserve"3,"][{]]]`
>  	Get(testJSON, testJSON)
>  
> +	// Issue #237
> +	testJSON1 := `["*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,,,,,,"]`
> +	testJSON2 := `#[%"*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,,,,,,""*,*"]`
> +	Get(testJSON1, testJSON2)
> +
> +}
> +
> +func TestSubpathsWithMultipaths(t *testing.T) {
> +	const json = `
> +[
> +  {"a": 1},
> +  {"a": 2, "values": ["a", "b", "c", "d", "e"]},
> +  true,
> +  ["a", "b", "c", "d", "e"],
> +  4
> +]
> +`
> +	assert(t, Get(json, `1.values.@ugly`).Raw == `["a","b","c","d","e"]`)
> +	assert(t, Get(json, `1.values.[0,3]`).Raw == `["a","d"]`)
> +	assert(t, Get(json, `3.@ugly`).Raw == `["a","b","c","d","e"]`)
> +	assert(t, Get(json, `3.[0,3]`).Raw == `["a","d"]`)
> +	assert(t, Get(json, `#.@ugly`).Raw == `[{"a":1},{"a":2,"values":["a","b","c","d","e"]},true,["a","b","c","d","e"],4]`)
> +	assert(t, Get(json, `#.[0,3]`).Raw == `[[],[],[],["a","d"],[]]`)
> +}
> +
> +func TestFlattenRemoveNonExist(t *testing.T) {
> +	raw := Get("[[1],[2,[[],[3]],[4,[5],[],[[[6]]]]]]", `@flatten:{"deep":true}`).Raw
> +	assert(t, raw == "[1,2,3,4,5,6]")
> +}
> +
> +func TestPipeEmptyArray(t *testing.T) {
> +	raw := Get("[]", `#(hello)#`).Raw
> +	assert(t, raw == "[]")
> +}
> +
> +func TestEncodedQueryString(t *testing.T) {
> +	json := `{
> +		"friends": [
> +			{"first": "Dale", "last": "Mur\nphy", "age": 44},
> +			{"first": "Roger", "last": "Craig", "age": 68},
> +			{"first": "Jane", "last": "Murphy", "age": 47}
> +		]
> +	}`
> +	assert(t, Get(json, `friends.#(last=="Mur\nphy").age`).Int() == 44)
> +	assert(t, Get(json, `friends.#(last=="Murphy").age`).Int() == 47)
> +}
> +
> +func TestBoolConvertQuery(t *testing.T) {
> +	json := `{
> +		"vals": [
> +			{ "a": 1, "b": true },
> +			{ "a": 2, "b": true },
> +			{ "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 }
> +		]
> +	}`
> +	trues := Get(json, `vals.#(b==~true)#.a`).Raw
> +	falses := Get(json, `vals.#(b==~false)#.a`).Raw
> +	assert(t, trues == "[1,2,6,7,8]")
> +	assert(t, falses == "[3,4,5,9,10,11]")
> +}
> +
> +func TestModifierDoubleQuotes(t *testing.T) {
> +	josn := `{
> +		"data": [
> +		  {
> +			"name": "Product P4",
> +			"productId": "1bb3",
> +			"vendorId": "10de"
> +		  },
> +		  {
> +			"name": "Product P4",
> +			"productId": "1cc3",
> +			"vendorId": "20de"
> +		  },
> +		  {
> +			"name": "Product P4",
> +			"productId": "1dd3",
> +			"vendorId": "30de"
> +		  }
> +		]
> +	  }`
> +	AddModifier("string", func(josn, arg string) string {
> +		return strconv.Quote(josn)
> +	})
> +
> +	res := Get(josn, "data.#.{name,value:{productId,vendorId}.@string.@ugly}")
> +
> +	assert(t, res.Raw == `[`+
> +		`{"name":"Product P4","value":"{\"productId\":\"1bb3\",\"vendorId\":\"10de\"}"},`+
> +		`{"name":"Product P4","value":"{\"productId\":\"1cc3\",\"vendorId\":\"20de\"}"},`+
> +		`{"name":"Product P4","value":"{\"productId\":\"1dd3\",\"vendorId\":\"30de\"}"}`+
> +		`]`)
> +
> +}
> +
> +func TestIndexes(t *testing.T) {
> +	var exampleJSON = `{
> +		"vals": [
> +			[1,66,{test: 3}],
> +			[4,5,[6]]
> +		],
> +		"objectArray":[
> +			{"first": "Dale", "age": 44},
> +			{"first": "Roger", "age": 68},
> +		]
> +	}`
> +
> +	testCases := []struct {
> +		path     string
> +		expected []string
> +	}{
> +		{
> +			`vals.#.1`,
> +			[]string{`6`, "5"},
> +		},
> +		{
> +			`vals.#.2`,
> +			[]string{"{", "["},
> +		},
> +		{
> +			`objectArray.#(age>43)#.first`,
> +			[]string{`"`, `"`},
> +		},
> +		{
> +			`objectArray.@reverse.#.first`,
> +			nil,
> +		},
> +	}
> +
> +	for _, tc := range testCases {
> +		r := Get(exampleJSON, tc.path)
> +
> +		assert(t, len(r.Indexes) == len(tc.expected))
> +
> +		for i, a := range r.Indexes {
> +			assert(t, string(exampleJSON[a]) == tc.expected[i])
> +		}
> +	}
> +}
> +
> +func TestIndexesMatchesRaw(t *testing.T) {
> +	var exampleJSON = `{
> +		"objectArray":[
> +			{"first": "Jason", "age": 41},
> +			{"first": "Dale", "age": 44},
> +			{"first": "Roger", "age": 68},
> +			{"first": "Mandy", "age": 32}
> +		]
> +	}`
> +	r := Get(exampleJSON, `objectArray.#(age>43)#.first`)
> +	assert(t, len(r.Indexes) == 2)
> +	assert(t, Parse(exampleJSON[r.Indexes[0]:]).String() == "Dale")
> +	assert(t, Parse(exampleJSON[r.Indexes[1]:]).String() == "Roger")
> +	r = Get(exampleJSON, `objectArray.#(age>43)#`)
> +	assert(t, Parse(exampleJSON[r.Indexes[0]:]).Get("first").String() == "Dale")
> +	assert(t, Parse(exampleJSON[r.Indexes[1]:]).Get("first").String() == "Roger")
> +}
> +
> +func TestIssue240(t *testing.T) {
> +	nonArrayData := `{"jsonrpc":"2.0","method":"subscription","params":
> +		{"channel":"funny_channel","data":
> +			{"name":"Jason","company":"good_company","number":12345}
> +		}
> +	}`
> +	parsed := Parse(nonArrayData)
> +	assert(t, len(parsed.Get("params.data").Array()) == 1)
> +
> +	arrayData := `{"jsonrpc":"2.0","method":"subscription","params":
> +		{"channel":"funny_channel","data":[
> +			{"name":"Jason","company":"good_company","number":12345}
> +		]}
> +	}`
> +	parsed = Parse(arrayData)
> +	assert(t, len(parsed.Get("params.data").Array()) == 1)
> +}
> +
> +func TestKeysValuesModifier(t *testing.T) {
> +	var json = `{
> +		"1300014": {
> +		  "code": "1300014",
> +		  "price": 59.18,
> +		  "symbol": "300014",
> +		  "update": "2020/04/15 15:59:54",
> +		},
> +		"1300015": {
> +		  "code": "1300015",
> +		  "price": 43.31,
> +		  "symbol": "300015",
> +		  "update": "2020/04/15 15:59:54",
> +		}
> +	  }`
> +	assert(t, Get(json, `@keys`).String() == `["1300014","1300015"]`)
> +	assert(t, Get(``, `@keys`).String() == `[]`)
> +	assert(t, Get(`"hello"`, `@keys`).String() == `[null]`)
> +	assert(t, Get(`[]`, `@keys`).String() == `[]`)
> +	assert(t, Get(`[1,2,3]`, `@keys`).String() == `[null,null,null]`)
> +
> +	assert(t, Get(json, `@values.#.code`).String() == `["1300014","1300015"]`)
> +	assert(t, Get(``, `@values`).String() == `[]`)
> +	assert(t, Get(`"hello"`, `@values`).String() == `["hello"]`)
> +	assert(t, Get(`[]`, `@values`).String() == `[]`)
> +	assert(t, Get(`[1,2,3]`, `@values`).String() == `[1,2,3]`)
> +}
> +
> +func TestNaNInf(t *testing.T) {
> +	json := `[+Inf,-Inf,Inf,iNF,-iNF,+iNF,NaN,nan,nAn,-0,+0]`
> +	raws := []string{"+Inf", "-Inf", "Inf", "iNF", "-iNF", "+iNF", "NaN", "nan",
> +		"nAn", "-0", "+0"}
> +	nums := []float64{math.Inf(+1), math.Inf(-1), math.Inf(0), math.Inf(0),
> +		math.Inf(-1), math.Inf(+1), math.NaN(), math.NaN(), math.NaN(),
> +		math.Copysign(0, -1), 0}
> +
> +	assert(t, int(Get(json, `#`).Int()) == len(raws))
> +	for i := 0; i < len(raws); i++ {
> +		r := Get(json, fmt.Sprintf("%d", i))
> +		assert(t, r.Raw == raws[i])
> +		assert(t, r.Num == nums[i] || (math.IsNaN(r.Num) && math.IsNaN(nums[i])))
> +		assert(t, r.Type == Number)
> +	}
> +
> +	var i int
> +	Parse(json).ForEach(func(_, r Result) bool {
> +		assert(t, r.Raw == raws[i])
> +		assert(t, r.Num == nums[i] || (math.IsNaN(r.Num) && math.IsNaN(nums[i])))
> +		assert(t, r.Type == Number)
> +		i++
> +		return true
> +	})
> +
> +	// Parse should also return valid numbers
> +	assert(t, math.IsNaN(Parse("nan").Float()))
> +	assert(t, math.IsNaN(Parse("NaN").Float()))
> +	assert(t, math.IsNaN(Parse(" NaN").Float()))
> +	assert(t, math.IsInf(Parse("+inf").Float(), +1))
> +	assert(t, math.IsInf(Parse("-inf").Float(), -1))
> +	assert(t, math.IsInf(Parse("+INF").Float(), +1))
> +	assert(t, math.IsInf(Parse("-INF").Float(), -1))
> +	assert(t, math.IsInf(Parse(" +INF").Float(), +1))
> +	assert(t, math.IsInf(Parse(" -INF").Float(), -1))
> +}
> +
> +func TestEmptyValueQuery(t *testing.T) {
> +	// issue: https://github.com/tidwall/gjson/issues/246
> +	assert(t, Get(
> +		`["ig","","tw","fb","tw","ig","tw"]`,
> +		`#(!="")#`).Raw ==
> +		`["ig","tw","fb","tw","ig","tw"]`)
> +	assert(t, Get(
> +		`["ig","","tw","fb","tw","ig","tw"]`,
> +		`#(!=)#`).Raw ==
> +		`["ig","tw","fb","tw","ig","tw"]`)
> +}
> +
> +func TestParseIndex(t *testing.T) {
> +	assert(t, Parse(`{}`).Index == 0)
> +	assert(t, Parse(` {}`).Index == 1)
> +	assert(t, Parse(` []`).Index == 1)
> +	assert(t, Parse(` true`).Index == 1)
> +	assert(t, Parse(` false`).Index == 1)
> +	assert(t, Parse(` null`).Index == 1)
> +	assert(t, Parse(` +inf`).Index == 1)
> +	assert(t, Parse(` -inf`).Index == 1)
> +}
> +
> +func TestRevSquash(t *testing.T) {
> +	assert(t, revSquash(` {}`) == `{}`)
> +	assert(t, revSquash(` }`) == ` }`)
> +	assert(t, revSquash(` [123]`) == `[123]`)
> +	assert(t, revSquash(` ,123,123]`) == ` ,123,123]`)
> +	assert(t, revSquash(` hello,[[true,false],[0,1,2,3,5],[123]]`) == `[[true,false],[0,1,2,3,5],[123]]`)
> +	assert(t, revSquash(` "hello"`) == `"hello"`)
> +	assert(t, revSquash(` "hel\\lo"`) == `"hel\\lo"`)
> +	assert(t, revSquash(` "hel\\"lo"`) == `"lo"`)
> +	assert(t, revSquash(` "hel\\\"lo"`) == `"hel\\\"lo"`)
> +	assert(t, revSquash(`hel\\\"lo"`) == `hel\\\"lo"`)
> +	assert(t, revSquash(`\"hel\\\"lo"`) == `\"hel\\\"lo"`)
> +	assert(t, revSquash(`\\\"hel\\\"lo"`) == `\\\"hel\\\"lo"`)
> +	assert(t, revSquash(`\\\\"hel\\\"lo"`) == `"hel\\\"lo"`)
> +	assert(t, revSquash(`hello"`) == `hello"`)
> +	json := `true,[0,1,"sadf\"asdf",{"hi":["hello","t\"\"u",{"a":"b"}]},9]`
> +	assert(t, revSquash(json) == json[5:])
> +	assert(t, revSquash(json[:len(json)-3]) == `{"hi":["hello","t\"\"u",{"a":"b"}]}`)
> +	assert(t, revSquash(json[:len(json)-4]) == `["hello","t\"\"u",{"a":"b"}]`)
> +	assert(t, revSquash(json[:len(json)-5]) == `{"a":"b"}`)
> +	assert(t, revSquash(json[:len(json)-6]) == `"b"`)
> +	assert(t, revSquash(json[:len(json)-10]) == `"a"`)
> +	assert(t, revSquash(json[:len(json)-15]) == `"t\"\"u"`)
> +	assert(t, revSquash(json[:len(json)-24]) == `"hello"`)
> +	assert(t, revSquash(json[:len(json)-33]) == `"hi"`)
> +	assert(t, revSquash(json[:len(json)-39]) == `"sadf\"asdf"`)
> +}
> +
> +const readmeJSON = `
> +{
> +  "name": {"first": "Tom", "last": "Anderson"},
> +  "age":37,
> +  "children": ["Sara","Alex","Jack"],
> +  "fav.movie": "Deer Hunter",
> +  "friends": [
> +    {"first": "Dale", "last": "Murphy", "age": 44, "nets": ["ig", "fb", "tw"]},
> +    {"first": "Roger", "last": "Craig", "age": 68, "nets": ["fb", "tw"]},
> +    {"first": "Jane", "last": "Murphy", "age": 47, "nets": ["ig", "tw"]}
> +  ]
> +}
> +`
> +
> +func TestQueryGetPath(t *testing.T) {
> +	assert(t, strings.Join(
> +		Get(readmeJSON, "friends.#.first").Paths(readmeJSON), " ") ==
> +		"friends.0.first friends.1.first friends.2.first")
> +	assert(t, strings.Join(
> +		Get(readmeJSON, "friends.#(last=Murphy)").Paths(readmeJSON), " ") ==
> +		"")
> +	assert(t, Get(readmeJSON, "friends.#(last=Murphy)").Path(readmeJSON) ==
> +		"friends.0")
> +	assert(t, strings.Join(
> +		Get(readmeJSON, "friends.#(last=Murphy)#").Paths(readmeJSON), " ") ==
> +		"friends.0 friends.2")
> +	arr := Get(readmeJSON, "friends.#.first").Array()
> +	for i := 0; i < len(arr); i++ {
> +		assert(t, arr[i].Path(readmeJSON) == fmt.Sprintf("friends.%d.first", i))
> +	}
> +}
> +
> +func TestStaticJSON(t *testing.T) {
> +	json := `{
> +		"name": {"first": "Tom", "last": "Anderson"}
> +	}`
> +	assert(t, Get(json,
> +		`"bar"`).Raw ==
> +		``)
> +	assert(t, Get(json,
> +		`!"bar"`).Raw ==
> +		`"bar"`)
> +	assert(t, Get(json,
> +		`!{"name":{"first":"Tom"}}.{name.first}.first`).Raw ==
> +		`"Tom"`)
> +	assert(t, Get(json,
> +		`{name.last,"foo":!"bar"}`).Raw ==
> +		`{"last":"Anderson","foo":"bar"}`)
> +	assert(t, Get(json,
> +		`{name.last,"foo":!{"a":"b"},"that"}`).Raw ==
> +		`{"last":"Anderson","foo":{"a":"b"}}`)
> +	assert(t, Get(json,
> +		`{name.last,"foo":!{"c":"d"},!"that"}`).Raw ==
> +		`{"last":"Anderson","foo":{"c":"d"},"_":"that"}`)
> +	assert(t, Get(json,
> +		`[!true,!false,!null,!inf,!nan,!hello,{"name":!"andy",name.last},+inf,!["any","thing"]]`).Raw ==
> +		`[true,false,null,inf,nan,{"name":"andy","last":"Anderson"},["any","thing"]]`,
> +	)
> +}
> +
> +func TestArrayKeys(t *testing.T) {
> +	N := 100
> +	json := "["
> +	for i := 0; i < N; i++ {
> +		if i > 0 {
> +			json += ","
> +		}
> +		json += fmt.Sprint(i)
> +	}
> +	json += "]"
> +	var i int
> +	Parse(json).ForEach(func(key, value Result) bool {
> +		assert(t, key.String() == fmt.Sprint(i))
> +		assert(t, key.Int() == int64(i))
> +		i++
> +		return true
> +	})
> +	assert(t, i == N)
> +}
> +
> +func TestToFromStr(t *testing.T) {
> +	json := `{"Message":"{\"Records\":[{\"eventVersion\":\"2.1\"}]"}`
> +	res := Get(json, "Message.@fromstr.Records.#.eventVersion.@tostr").Raw
> +	assert(t, res == `["\"2.1\""]`)
> +}
> +
> +func TestGroup(t *testing.T) {
> +	json := `{"id":["123","456","789"],"val":[2,1]}`
> +	res := Get(json, "@group").Raw
> +	assert(t, res == `[{"id":"123","val":2},{"id":"456","val":1},{"id":"789"}]`)
> +
> +	json = `
> +{
> +	"issues": [
> +	  {
> +		"fields": {
> +		  "labels": [
> +			"milestone_1",
> +			"group:foo",
> +			"plan:a",
> +			"plan:b"
> +		  ]
> +		},
> +		"id": "123"
> +	  },{
> +		"fields": {
> +		  "labels": [
> +			"milestone_1",
> +			"group:foo",
> +			"plan:a",
> +			"plan"
> +		  ]
> +		},
> +		"id": "456"
> +	  }
> +	]
> +  }
> +  `
> +	res = Get(json, `{"id":issues.#.id,"plans":issues.#.fields.labels.#(%"plan:*")#|#.#}|@group|#(plans>=2)#.id`).Raw
> +	assert(t, res == `["123"]`)
> +}
> +
> +func testJSONString(t *testing.T, str string) {
> +	gjsonString := string(AppendJSONString(nil, str))
> +	data, err := json.Marshal(str)
> +	if err != nil {
> +		panic(123)
> +	}
> +	goString := string(data)
> +	if gjsonString != goString {
> +		t.Fatal(strconv.Quote(str) + "\n\t" +
> +			gjsonString + "\n\t" +
> +			goString + "\n\t<<< MISMATCH >>>")
> +	}
> +}
> +
> +func TestJSONString(t *testing.T) {
> +	testJSONString(t, "hello")
> +	testJSONString(t, "he\"llo")
> +	testJSONString(t, "he\"l\\lo")
> +	const input = `{"utf8":"Example emoji, KO: \ud83d\udd13, \ud83c\udfc3 ` +
> +		`OK: \u2764\ufe0f "}`
> +	value := Get(input, "utf8")
> +	var s string
> +	json.Unmarshal([]byte(value.Raw), &s)
> +	if value.String() != s {
> +		t.Fatalf("expected '%v', got '%v'", s, value.String())
> +	}
> +	testJSONString(t, s)
> +	testJSONString(t, "R\xfd\xfc\a!\x82eO\x16?_\x0f\x9ab\x1dr")
> +	testJSONString(t, "_\xb9\v\xad\xb3|X!\xb6\xd9U&\xa4\x1a\x95\x04")
> +	rng := rand.New(rand.NewSource(time.Now().UnixNano()))
> +	start := time.Now()
> +	var buf [16]byte
> +	for time.Since(start) < time.Second*2 {
> +		if _, err := rng.Read(buf[:]); err != nil {
> +			t.Fatal(err)
> +		}
> +		testJSONString(t, string(buf[:]))
> +	}
> +}
> +
> +func TestIndexAtSymbol(t *testing.T) {
> +	json := `{
> +		"@context": {
> +		  "rdfs": "http://www.w3.org/2000/01/rdf-schema#";,
> +		  "@vocab": "http://schema.org/";,
> +		  "sh": "http://www.w3.org/ns/shacl#";
> +		}
> +	}`
> +	assert(t, Get(json, "@context.@vocab").Index == 85)
> +}
> +
> +func TestDeepModifierWithOptions(t *testing.T) {
> +	rawJson := `{"x":[{"y":[{"z":{"b":1, "c": 2, "a": 3}}]}]}`
> +	jsonPathExpr := `x.#.y.#.z.@pretty:{"sortKeys":true}`
> +	results := GetManyBytes([]byte(rawJson), jsonPathExpr)
> +	assert(t, len(results) == 1)
> +	actual := results[0].Raw
> +	expected := `[[{
> +  "a": 3,
> +  "b": 1,
> +  "c": 2
> +}
> +]]`
> +	if expected != actual {
> +		t.Fatal(strconv.Quote(rawJson) + "\n\t" +
> +			expected + "\n\t" +
> +			actual + "\n\t<<< MISMATCH >>>")
> +	}
> +}
> +
> +func TestIssue301(t *testing.T) {
> +	json := `{
> +		"children": ["Sara","Alex","Jack"],
> +		"fav.movie": ["Deer Hunter"]
> +	}`
> +
> +	assert(t, Get(json, `children.0`).String() == "Sara")
> +	assert(t, Get(json, `children.[0]`).String() == `["Sara"]`)
> +	assert(t, Get(json, `children.1`).String() == "Alex")
> +	assert(t, Get(json, `children.[1]`).String() == `["Alex"]`)
> +	assert(t, Get(json, `children.[10]`).String() == `[]`)
> +	assert(t, Get(json, `fav\.movie.0`).String() == "Deer Hunter")
> +	assert(t, Get(json, `fav\.movie.[0]`).String() == `["Deer Hunter"]`)
> +	assert(t, Get(json, `fav\.movie.1`).String() == "")
> +	assert(t, Get(json, `fav\.movie.[1]`).String() == "[]")
> +
>  }
> diff --git a/go.mod b/go.mod
> index 30d1804..6f64083 100644
> --- a/go.mod
> +++ b/go.mod
> @@ -3,6 +3,6 @@ module github.com/tidwall/gjson
>  go 1.12
>  
>  require (
> -	github.com/tidwall/match v1.0.3
> -	github.com/tidwall/pretty v1.0.2
> +	github.com/tidwall/match v1.1.1
> +	github.com/tidwall/pretty v1.2.0
>  )
> diff --git a/go.sum b/go.sum
> index 1eb2014..be39c8c 100644
> --- a/go.sum
> +++ b/go.sum
> @@ -1,6 +1,4 @@
> -github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc=
> -github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
> -github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE=
> -github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
> -github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU=
> -github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
> +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
> +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
> +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
> +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=


-- 
Sebastian Ramacher


Reply to: