diff --git a/gjson/LICENSE b/gjson/LICENSE
deleted file mode 100644
index 58f5819a4..000000000
--- a/gjson/LICENSE
+++ /dev/null
@@ -1,20 +0,0 @@
-The MIT License (MIT)
-
-Copyright (c) 2016 Josh Baker
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of
-this software and associated documentation files (the "Software"), to deal in
-the Software without restriction, including without limitation the rights to
-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
-the Software, and to permit persons to whom the Software is furnished to do so,
-subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
-FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
-COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
-IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/gjson/README.md b/gjson/README.md
deleted file mode 100644
index 4108deb37..000000000
--- a/gjson/README.md
+++ /dev/null
@@ -1,495 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-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).
-
-Also check out [SJSON](https://github.com/tidwall/sjson) for modifying json, and the [JJ](https://github.com/tidwall/jj) command line tool.
-
-Getting Started
-===============
-
-## Installing
-
-To start using GJSON, install Go and run `go get`:
-
-```sh
-$ 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.
-
-```go
-package main
-
-import "github.com/tidwall/gjson"
-
-const json = `{"name":{"first":"Janet","last":"Prichard"},"age":47}`
-
-func main() {
- value := gjson.Get(json, "name.last")
- println(value.String())
-}
-```
-
-This will print:
-
-```
-Prichard
-```
-*There's also the [GetMany](#get-multiple-values-at-once) function to get multiple values at once, and [GetBytes](#working-with-bytes) for working with JSON byte slices.*
-
-## Path Syntax
-
-Below is a quick overview of the path syntax, for more complete information please
-check out [GJSON Syntax](SYNTAX.md).
-
-A path is a series of keys separated by a dot.
-A key may contain special wildcard characters '\*' and '?'.
-To access an array value use the index as the key.
-To get the number of elements in an array or to access a child path, use the '#' character.
-The dot and wildcard characters can be escaped with '\\'.
-
-```json
-{
- "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"]}
- ]
-}
-```
-```
-"name.last" >> "Anderson"
-"age" >> 37
-"children" >> ["Sara","Alex","Jack"]
-"children.#" >> 3
-"children.1" >> "Alex"
-"child*.2" >> "Jack"
-"c?ildren.0" >> "Sara"
-"fav\.movie" >> "Deer Hunter"
-"friends.#.first" >> ["Dale","Roger","Jane"]
-"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 `!%`
-(not like) operators.
-
-```
-friends.#(last=="Murphy").first >> "Dale"
-friends.#(last=="Murphy")#.first >> ["Dale","Jane"]
-friends.#(age>45)#.last >> ["Craig","Murphy"]
-friends.#(first%"D*").last >> "Murphy"
-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
-changed in v1.3.0 as to avoid confusion with the new
-[multipath](SYNTAX.md#multipaths) 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.
-
-The `Result` type holds one of these:
-
-```
-bool, for JSON booleans
-float64, for JSON numbers
-string, for JSON string literals
-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
-```
-
-There are a variety of handy functions that work on a result:
-
-```go
-result.Exists() bool
-result.Value() interface{}
-result.Int() int64
-result.Uint() uint64
-result.Float() float64
-result.String() string
-result.Bool() bool
-result.Time() time.Time
-result.Array() []gjson.Result
-result.Map() map[string]gjson.Result
-result.Get(path string) Result
-result.ForEach(iterator func(key, value Result) bool)
-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
-string >> string
-null >> nil
-array >> []interface{}
-object >> map[string]interface{}
-```
-
-### 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
-```
-
-## 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.
-
-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,
-we'll get `children` array and reverse the order:
-
-```
-"children|@reverse" >> ["Jack","Alex","Sara"]
-"children|@reverse|0" >> "Jack"
-```
-
-There 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.
-- `@this`: Returns the current element. It can be used to retrieve the root element.
-- `@valid`: Ensure the json document is valid.
-- `@flatten`: Flattens an array.
-- `@join`: Joins multiple objects into a single object.
-
-### Modifier arguments
-
-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.
-
-```
-@pretty:{"sortKeys":true}
-```
-
-Which makes the json pretty and orders all of its keys.
-
-```json
-{
- "age":37,
- "children": ["Sara","Alex","Jack"],
- "fav.movie": "Deer Hunter",
- "friends": [
- {"age": 44, "first": "Dale", "last": "Murphy"},
- {"age": 68, "first": "Roger", "last": "Craig"},
- {"age": 47, "first": "Jane", "last": "Murphy"}
- ],
- "name": {"first": "Tom", "last": "Anderson"}
-}
-```
-
-*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
-or lower case.
-
-```go
-gjson.AddModifier("case", func(json, arg string) string {
- if arg == "upper" {
- return strings.ToUpper(json)
- }
- if arg == "lower" {
- return strings.ToLower(json)
- }
- return json
-})
-```
-
-```
-"children|@case:upper" >> ["SARA","ALEX","JACK"]
-"children|@case:lower|@reverse" >> ["jack","alex","sara"]
-```
-
-## JSON Lines
-
-There's support for [JSON Lines](http://jsonlines.org/) using the `..` prefix, which treats a multilined document as an array.
-
-For example:
-
-```
-{"name": "Gilbert", "age": 61}
-{"name": "Alexa", "age": 34}
-{"name": "May", "age": 57}
-{"name": "Deloise", "age": 44}
-```
-
-```
-..# >> 4
-..1 >> {"name": "Alexa", "age": 34}
-..3 >> {"name": "Deloise", "age": 44}
-..#.name >> ["Gilbert","Alexa","May","Deloise"]
-..#(name="May").age >> 57
-```
-
-The `ForEachLines` function will iterate through JSON lines.
-
-```go
-gjson.ForEachLine(json, func(line gjson.Result) bool{
- println(line.String())
- return true
-})
-```
-
-## Get nested array values
-
-Suppose you want all the last names from the following json:
-
-```json
-{
- "programmers": [
- {
- "firstName": "Janet",
- "lastName": "McLaughlin",
- }, {
- "firstName": "Elliotte",
- "lastName": "Hunter",
- }, {
- "firstName": "Jason",
- "lastName": "Harold",
- }
- ]
-}
-```
-
-You would use the path "programmers.#.lastName" like such:
-
-```go
-result := gjson.Get(json, "programmers.#.lastName")
-for _, name := range result.Array() {
- println(name.String())
-}
-```
-
-You can also query an object inside an array:
-
-```go
-name := gjson.Get(json, `programmers.#(lastName="Hunter").firstName`)
-println(name.String()) // prints "Elliotte"
-```
-
-## Iterate 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.
-
-```go
-result := gjson.Get(json, "programmers")
-result.ForEach(func(key, value gjson.Result) bool {
- println(value.String())
- return true // keep iterating
-})
-```
-
-## Simple Parse and Get
-
-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:
-
-```go
-gjson.Parse(json).Get("name").Get("last")
-gjson.Get(json, "name").Get("last")
-gjson.Get(json, "name.last")
-```
-
-## Check for the existence of a value
-
-Sometimes you just want to know if a value exists.
-
-```go
-value := gjson.Get(json, "name.last")
-if !value.Exists() {
- println("no last name")
-} else {
- println(value.String())
-}
-
-// Or as one step
-if gjson.Get(json, "name.last").Exists() {
- println("has a last name")
-}
-```
-
-## 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.
-
-If you are consuming JSON from an unpredictable source then you may want to validate prior to using GJSON.
-
-```go
-if !gjson.Valid(json) {
- return errors.New("invalid json")
-}
-value := gjson.Get(json, "name.last")
-```
-
-## Unmarshal to a map
-
-To unmarshal to a `map[string]interface{}`:
-
-```go
-m, ok := gjson.Parse(json).Value().(map[string]interface{})
-if !ok {
- // not a map
-}
-```
-
-## Working with Bytes
-
-If your JSON is contained in a `[]byte` slice, there's the [GetBytes](https://godoc.org/github.com/tidwall/gjson#GetBytes) function. This is preferred over `Get(string(data), path)`.
-
-```go
-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:
-
-```go
-var json []byte = ...
-result := gjson.GetBytes(json, path)
-var raw []byte
-if result.Index > 0 {
- raw = json[result.Index:result.Index+len(result.Raw)]
-} else {
- raw = []byte(result.Raw)
-}
-```
-
-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`.
-
-## Get multiple values at once
-
-The `GetMany` function can be used to get multiple values at the same time.
-
-```go
-results := gjson.GetMany(json, "name.first", "name.last", "age")
-```
-
-The return value is a `[]Result`, which will always contain exactly the same number of items as the input paths.
-
-## Performance
-
-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)
-
-```
-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
-```
-
-JSON document used:
-
-```json
-{
- "widget": {
- "debug": "on",
- "window": {
- "title": "Sample Konfabulator Widget",
- "name": "main_window",
- "width": 500,
- "height": 500
- },
- "image": {
- "src": "Images/Sun.png",
- "hOffset": 250,
- "vOffset": 250,
- "alignment": "center"
- },
- "text": {
- "data": "Click Here",
- "size": 36,
- "style": "bold",
- "vOffset": 100,
- "alignment": "center",
- "onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;"
- }
- }
-}
-```
-
-Each operation was rotated though one of the following search paths:
-
-```
-widget.window.name
-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 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).
diff --git a/gjson/SYNTAX.md b/gjson/SYNTAX.md
deleted file mode 100644
index 9558019e7..000000000
--- a/gjson/SYNTAX.md
+++ /dev/null
@@ -1,269 +0,0 @@
-# GJSON Path Syntax
-
-A GJSON Path is a text string syntax that describes a search pattern for quickly retreiving values from a JSON payload.
-
-This document is designed to explain the structure of a GJSON Path through examples.
-
-- [Path structure](#path-structure)
-- [Basic](#basic)
-- [Wildcards](#wildcards)
-- [Escape Character](#escape-character)
-- [Arrays](#arrays)
-- [Queries](#queries)
-- [Dot vs Pipe](#dot-vs-pipe)
-- [Modifiers](#modifiers)
-- [Multipaths](#multipaths)
-
-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 `?`.
-
-## Example
-
-Given this JSON
-
-```json
-{
- "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"]}
- ]
-}
-```
-
-The following GJSON Paths evaluate to the accompanying values.
-
-### Basic
-
-In many cases you'll just want to retreive values by object name or array index.
-
-```go
-name.last "Anderson"
-name.first "Tom"
-age 37
-children ["Sara","Alex","Jack"]
-children.0 "Sara"
-children.1 "Alex"
-friends.1 {"first": "Roger", "last": "Craig", "age": 68}
-friends.1.first "Roger"
-```
-
-### Wildcards
-
-A key may contain the special wildcard characters `*` and `?`.
-The `*` will match on any zero+ characters, and `?` matches on any one character.
-
-```go
-child*.2 "Jack"
-c?ildren.0 "Sara"
-```
-
-### Escape character
-
-Special purpose characters, such as `.`, `*`, and `?` can be escaped with `\`.
-
-```go
-fav\.movie "Deer Hunter"
-```
-
-### Arrays
-
-The `#` character allows for digging into JSON Arrays.
-
-To get the length of an array you'll just use the `#` all by itself.
-
-```go
-friends.# 3
-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,
-and the simple pattern matching `%` (like) and `!%` (not like) operators.
-
-```go
-friends.#(last=="Murphy").first "Dale"
-friends.#(last=="Murphy")#.first ["Dale","Jane"]
-friends.#(age>45)#.last ["Craig","Murphy"]
-friends.#(first%"D*").last "Murphy"
-friends.#(first!%"D*").last "Craig"
-```
-
-To query for a non-object value in an array, you can forgo the string to the right of the operator.
-
-```go
-children.#(!%"*a*") "Alex"
-children.#(%"*a*")# ["Sara","Jack"]
-```
-
-Nested queries are allowed.
-
-```go
-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)
-syntax. For backwards compatibility, `#[...]` will continue to work until the
-next major release.*
-
-### 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).
-
-Here are some examples
-
-```go
-friends.0.first "Dale"
-friends|0.first "Dale"
-friends.0|first "Dale"
-friends|0|first "Dale"
-friends|# 3
-friends.# 3
-friends.#(last="Murphy")# [{"first": "Dale", "last": "Murphy", "age": 44},{"first": "Jane", "last": "Murphy", "age": 47}]
-friends.#(last="Murphy")#.first ["Dale","Jane"]
-friends.#(last="Murphy")#|first
-friends.#(last="Murphy")#.0 []
-friends.#(last="Murphy")#|0 {"first": "Dale", "last": "Murphy", "age": 44}
-friends.#(last="Murphy")#.# []
-friends.#(last="Murphy")#|# 2
-```
-
-Let's break down a few of these.
-
-The path `friends.#(last="Murphy")#` all by itself results in
-
-```json
-[{"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
-
-```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
-because `first` does not exist.
-
-Yet, `|0` suffix returns
-
-```json
-{"first": "Dale", "last": "Murphy", "age": 44}
-```
-
-Because `0` is the first index of the previous result.
-
-### Modifiers
-
-A modifier is a path component that performs custom processing on the JSON.
-
-For example, using the built-in `@reverse` modifier on the above JSON payload will reverse the `children` array:
-
-```go
-children.@reverse ["Jack","Alex","Sara"]
-children.@reverse.0 "Jack"
-```
-
-There 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.
-- `@this`: Returns the current element. It can be used to retrieve the root element.
-- `@valid`: Ensure the json document is valid.
-- `@flatten`: Flattens an array.
-- `@join`: Joins multiple objects into a single object.
-
-#### Modifier arguments
-
-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.
-
-```
-@pretty:{"sortKeys":true}
-```
-
-Which makes the json pretty and orders all of its keys.
-
-```json
-{
- "age":37,
- "children": ["Sara","Alex","Jack"],
- "fav.movie": "Deer Hunter",
- "friends": [
- {"age": 44, "first": "Dale", "last": "Murphy"},
- {"age": 68, "first": "Roger", "last": "Craig"},
- {"age": 47, "first": "Jane", "last": "Murphy"}
- ],
- "name": {"first": "Tom", "last": "Anderson"}
-}
-```
-
-*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 which makes the entire JSON payload upper or lower case.
-
-```go
-gjson.AddModifier("case", func(json, arg string) string {
- if arg == "upper" {
- return strings.ToUpper(json)
- }
- if arg == "lower" {
- return strings.ToLower(json)
- }
- return json
-})
-"children.@case:upper" ["SARA","ALEX","JACK"]
-"children.@case:lower.@reverse" ["jack","alex","sara"]
-```
-
-### 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.
-
-For example, using the given multipath
-
-```
-{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".
-
-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
-
-```
-{"first":"Tom","age":37,"the_murphys":["Dale","Jane"]}
-```
-
-
diff --git a/gjson/gjson.go b/gjson/gjson.go
deleted file mode 100644
index 647ed735e..000000000
--- a/gjson/gjson.go
+++ /dev/null
@@ -1,2824 +0,0 @@
-// Package gjson provides searching for json strings.
-package gjson
-
-import (
- "encoding/json"
- "strconv"
- "strings"
- "time"
- "unicode/utf16"
- "unicode/utf8"
-
- "github.com/tidwall/match"
- "github.com/tidwall/pretty"
-)
-
-// Type is Result type
-type Type int
-
-const (
- // Null is a null json value
- Null Type = iota
- // False is a json false boolean
- False
- // Number is json number
- Number
- // String is a json string
- String
- // True is a json true boolean
- True
- // JSON is a raw block of JSON
- JSON
-)
-
-// String returns a string representation of the type.
-func (t Type) String() string {
- switch t {
- default:
- return ""
- case Null:
- return "Null"
- case False:
- return "False"
- case Number:
- return "Number"
- case String:
- return "String"
- case True:
- return "True"
- case JSON:
- return "JSON"
- }
-}
-
-// Result represents a json value that is returned from Get().
-type Result struct {
- // Type is the json type
- Type Type
- // Raw is the raw json
- Raw string
- // Str is the json string
- Str string
- // Num is the json number
- Num float64
- // Index of raw value in original json, zero means index unknown
- Index int
-}
-
-// String returns a string representation of the value.
-func (t Result) String() string {
- switch t.Type {
- default:
- return ""
- case False:
- return "false"
- case Number:
- if len(t.Raw) == 0 {
- // calculated result
- return strconv.FormatFloat(t.Num, 'f', -1, 64)
- }
- var i int
- if t.Raw[0] == '-' {
- i++
- }
- for ; i < len(t.Raw); i++ {
- if t.Raw[i] < '0' || t.Raw[i] > '9' {
- return strconv.FormatFloat(t.Num, 'f', -1, 64)
- }
- }
- return t.Raw
- case String:
- return t.Str
- case JSON:
- return t.Raw
- case True:
- return "true"
- }
-}
-
-// Bool returns an boolean representation.
-func (t Result) Bool() bool {
- switch t.Type {
- default:
- return false
- case True:
- return true
- case String:
- return t.Str != "" && t.Str != "0" && t.Str != "false"
- case Number:
- return t.Num != 0
- }
-}
-
-// Int returns an integer representation.
-func (t Result) Int() int64 {
- switch t.Type {
- default:
- return 0
- case True:
- return 1
- case String:
- n, _ := parseInt(t.Str)
- return n
- case Number:
- // try to directly convert the float64 to int64
- n, ok := floatToInt(t.Num)
- if !ok {
- // now try to parse the raw string
- n, ok = parseInt(t.Raw)
- if !ok {
- // fallback to a standard conversion
- return int64(t.Num)
- }
- }
- return n
- }
-}
-
-// Uint returns an unsigned integer representation.
-func (t Result) Uint() uint64 {
- switch t.Type {
- default:
- return 0
- case True:
- return 1
- case String:
- n, _ := parseUint(t.Str)
- return n
- case Number:
- // try to directly convert the float64 to uint64
- n, ok := floatToUint(t.Num)
- if !ok {
- // now try to parse the raw string
- n, ok = parseUint(t.Raw)
- if !ok {
- // fallback to a standard conversion
- return uint64(t.Num)
- }
- }
- return n
- }
-}
-
-// Float returns an float64 representation.
-func (t Result) Float() float64 {
- switch t.Type {
- default:
- return 0
- case True:
- return 1
- case String:
- n, _ := strconv.ParseFloat(t.Str, 64)
- return n
- case Number:
- return t.Num
- }
-}
-
-// Time returns a time.Time representation.
-func (t Result) Time() time.Time {
- res, _ := time.Parse(time.RFC3339, t.String())
- return res
-}
-
-// 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
-// array containing one result.
-func (t Result) Array() []Result {
- if t.Type == Null {
- return []Result{}
- }
- if t.Type != JSON {
- return []Result{t}
- }
- r := t.arrayOrMap('[', false)
- return r.a
-}
-
-// IsObject returns true if the result value is a JSON object.
-func (t Result) IsObject() bool {
- return t.Type == JSON && len(t.Raw) > 0 && t.Raw[0] == '{'
-}
-
-// IsArray returns true if the result value is a JSON array.
-func (t Result) IsArray() bool {
- return t.Type == JSON && len(t.Raw) > 0 && t.Raw[0] == '['
-}
-
-// 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
-// value of each item. If the result is an Array, the iterator will only pass
-// the value of each item. If the result is not a JSON array or object, the
-// iterator will pass back one value equal to the result.
-func (t Result) ForEach(iterator func(key, value Result) bool) {
- if !t.Exists() {
- return
- }
- if t.Type != JSON {
- iterator(Result{}, t)
- return
- }
- json := t.Raw
- var keys bool
- var i int
- var key, value Result
- for ; i < len(json); i++ {
- if json[i] == '{' {
- i++
- key.Type = String
- keys = true
- break
- } else if json[i] == '[' {
- i++
- break
- }
- if json[i] > ' ' {
- return
- }
- }
- var str string
- var vesc bool
- var ok bool
- for ; i < len(json); i++ {
- if keys {
- if json[i] != '"' {
- continue
- }
- s := i
- i, str, vesc, ok = parseString(json, i+1)
- if !ok {
- return
- }
- if vesc {
- key.Str = unescape(str[1 : len(str)-1])
- } else {
- key.Str = str[1 : len(str)-1]
- }
- key.Raw = str
- key.Index = s
- }
- for ; i < len(json); i++ {
- if json[i] <= ' ' || json[i] == ',' || json[i] == ':' {
- continue
- }
- break
- }
- s := i
- i, value, ok = parseAny(json, i, true)
- if !ok {
- return
- }
- value.Index = s
- if !iterator(key, value) {
- return
- }
- }
-}
-
-// Map returns back an map of values. The result should be a JSON array.
-func (t Result) Map() map[string]Result {
- if t.Type != JSON {
- return map[string]Result{}
- }
- r := t.arrayOrMap('{', false)
- return r.o
-}
-
-// 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)
-}
-
-type arrayOrMapResult struct {
- a []Result
- ai []interface{}
- o map[string]Result
- oi map[string]interface{}
- vc byte
-}
-
-func (t Result) arrayOrMap(vc byte, valueize bool) (r arrayOrMapResult) {
- var json = t.Raw
- var i int
- var value Result
- var count int
- var key Result
- if vc == 0 {
- for ; i < len(json); i++ {
- if json[i] == '{' || json[i] == '[' {
- r.vc = json[i]
- i++
- break
- }
- if json[i] > ' ' {
- goto end
- }
- }
- } else {
- for ; i < len(json); i++ {
- if json[i] == vc {
- i++
- break
- }
- if json[i] > ' ' {
- goto end
- }
- }
- r.vc = vc
- }
- if r.vc == '{' {
- if valueize {
- r.oi = make(map[string]interface{})
- } else {
- r.o = make(map[string]Result)
- }
- } else {
- if valueize {
- r.ai = make([]interface{}, 0)
- } else {
- r.a = make([]Result, 0)
- }
- }
- for ; i < len(json); i++ {
- if json[i] <= ' ' {
- continue
- }
- // get next value
- if json[i] == ']' || json[i] == '}' {
- break
- }
- switch json[i] {
- default:
- if (json[i] >= '0' && json[i] <= '9') || json[i] == '-' {
- value.Type = Number
- value.Raw, value.Num = tonum(json[i:])
- value.Str = ""
- } else {
- continue
- }
- case '{', '[':
- value.Type = JSON
- value.Raw = squash(json[i:])
- value.Str, value.Num = "", 0
- case 'n':
- value.Type = Null
- value.Raw = tolit(json[i:])
- value.Str, value.Num = "", 0
- case 't':
- value.Type = True
- value.Raw = tolit(json[i:])
- value.Str, value.Num = "", 0
- case 'f':
- value.Type = False
- value.Raw = tolit(json[i:])
- value.Str, value.Num = "", 0
- case '"':
- value.Type = String
- value.Raw, value.Str = tostr(json[i:])
- value.Num = 0
- }
- i += len(value.Raw) - 1
-
- if r.vc == '{' {
- if count%2 == 0 {
- key = value
- } else {
- if valueize {
- if _, ok := r.oi[key.Str]; !ok {
- r.oi[key.Str] = value.Value()
- }
- } else {
- if _, ok := r.o[key.Str]; !ok {
- r.o[key.Str] = value
- }
- }
- }
- count++
- } else {
- if valueize {
- r.ai = append(r.ai, value.Value())
- } else {
- r.a = append(r.a, value)
- }
- }
- }
-end:
- return
-}
-
-// 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.
-// If you are consuming JSON from an unpredictable source then you may want to
-// use the Valid function first.
-func Parse(json string) Result {
- var value Result
- for i := 0; i < len(json); i++ {
- if json[i] == '{' || json[i] == '[' {
- value.Type = JSON
- value.Raw = json[i:] // just take the entire raw
- break
- }
- if json[i] <= ' ' {
- continue
- }
- switch json[i] {
- default:
- if (json[i] >= '0' && json[i] <= '9') || json[i] == '-' {
- value.Type = Number
- value.Raw, value.Num = tonum(json[i:])
- } else {
- return Result{}
- }
- case 'n':
- value.Type = Null
- value.Raw = tolit(json[i:])
- case 't':
- value.Type = True
- value.Raw = tolit(json[i:])
- case 'f':
- value.Type = False
- value.Raw = tolit(json[i:])
- case '"':
- value.Type = String
- value.Raw, value.Str = tostr(json[i:])
- }
- break
- }
- return value
-}
-
-// 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))
-}
-
-func squash(json string) string {
- // expects that the lead character is a '[' or '{' or '(' or '"'
- // squash the value, ignoring all nested arrays and objects.
- var i, depth int
- if json[0] != '"' {
- i, depth = 1, 1
- }
- for ; i < len(json); i++ {
- if json[i] >= '"' && json[i] <= '}' {
- switch json[i] {
- case '"':
- i++
- s2 := i
- for ; i < len(json); i++ {
- if json[i] > '\\' {
- continue
- }
- if json[i] == '"' {
- // look for an escaped slash
- if json[i-1] == '\\' {
- n := 0
- for j := i - 2; j > s2-1; j-- {
- if json[j] != '\\' {
- break
- }
- n++
- }
- if n%2 == 0 {
- continue
- }
- }
- break
- }
- }
- if depth == 0 {
- return json[:i+1]
- }
- case '{', '[', '(':
- depth++
- case '}', ']', ')':
- depth--
- if depth == 0 {
- return json[:i+1]
- }
- }
- }
- }
- return json
-}
-
-func tonum(json string) (raw string, num float64) {
- for i := 1; i < len(json); i++ {
- // less than dash might have valid characters
- if json[i] <= '-' {
- if json[i] <= ' ' || json[i] == ',' {
- // break on whitespace and comma
- raw = json[:i]
- num, _ = strconv.ParseFloat(raw, 64)
- return
- }
- // could be a '+' or '-'. let's assume so.
- continue
- }
- if json[i] < ']' {
- // probably a valid number
- continue
- }
- if json[i] == 'e' || json[i] == 'E' {
- // allow for exponential numbers
- continue
- }
- // likely a ']' or '}'
- raw = json[:i]
- num, _ = strconv.ParseFloat(raw, 64)
- return
- }
- raw = json
- num, _ = strconv.ParseFloat(raw, 64)
- return
-}
-
-func tolit(json string) (raw string) {
- for i := 1; i < len(json); i++ {
- if json[i] < 'a' || json[i] > 'z' {
- return json[:i]
- }
- }
- return json
-}
-
-func tostr(json string) (raw string, str string) {
- // expects that the lead character is a '"'
- for i := 1; i < len(json); i++ {
- if json[i] > '\\' {
- continue
- }
- if json[i] == '"' {
- return json[:i+1], json[1:i]
- }
- if json[i] == '\\' {
- i++
- for ; i < len(json); i++ {
- if json[i] > '\\' {
- continue
- }
- if json[i] == '"' {
- // look for an escaped slash
- if json[i-1] == '\\' {
- n := 0
- for j := i - 2; j > 0; j-- {
- if json[j] != '\\' {
- break
- }
- n++
- }
- if n%2 == 0 {
- continue
- }
- }
- break
- }
- }
- var ret string
- if i+1 < len(json) {
- ret = json[:i+1]
- } else {
- ret = json[:i]
- }
- return ret, unescape(json[1:i])
- }
- }
- return json, json[1:]
-}
-
-// Exists returns true if value exists.
-//
-// if gjson.Get(json, "name.last").Exists(){
-// println("value exists")
-// }
-func (t Result) Exists() bool {
- return t.Type != Null || len(t.Raw) != 0
-}
-
-// Value returns one of these types:
-//
-// bool, for JSON booleans
-// float64, for JSON numbers
-// Number, for JSON numbers
-// string, for JSON string literals
-// nil, for JSON null
-// map[string]interface{}, for JSON objects
-// []interface{}, for JSON arrays
-//
-func (t Result) Value() interface{} {
- if t.Type == String {
- return t.Str
- }
- switch t.Type {
- default:
- return nil
- case False:
- return false
- case Number:
- return t.Num
- case JSON:
- r := t.arrayOrMap(0, true)
- if r.vc == '{' {
- return r.oi
- } else if r.vc == '[' {
- return r.ai
- }
- return nil
- case True:
- return true
- }
-}
-
-func parseString(json string, i int) (int, string, bool, bool) {
- var s = i
- for ; i < len(json); i++ {
- if json[i] > '\\' {
- continue
- }
- if json[i] == '"' {
- return i + 1, json[s-1 : i+1], false, true
- }
- if json[i] == '\\' {
- i++
- for ; i < len(json); i++ {
- if json[i] > '\\' {
- continue
- }
- if json[i] == '"' {
- // look for an escaped slash
- if json[i-1] == '\\' {
- n := 0
- for j := i - 2; j > 0; j-- {
- if json[j] != '\\' {
- break
- }
- n++
- }
- if n%2 == 0 {
- continue
- }
- }
- return i + 1, json[s-1 : i+1], true, true
- }
- }
- break
- }
- }
- return i, json[s-1:], false, false
-}
-
-func parseNumber(json string, i int) (int, string) {
- var s = i
- i++
- for ; i < len(json); i++ {
- if json[i] <= ' ' || json[i] == ',' || json[i] == ']' ||
- json[i] == '}' {
- return i, json[s:i]
- }
- }
- return i, json[s:]
-}
-
-func parseLiteral(json string, i int) (int, string) {
- var s = i
- i++
- for ; i < len(json); i++ {
- if json[i] < 'a' || json[i] > 'z' {
- return i, json[s:i]
- }
- }
- return i, json[s:]
-}
-
-type arrayPathResult struct {
- part string
- path string
- pipe string
- piped bool
- more bool
- alogok bool
- arrch bool
- alogkey string
- query struct {
- on bool
- path string
- op string
- value string
- all bool
- }
-}
-
-func parseArrayPath(path string) (r arrayPathResult) {
- for i := 0; i < len(path); i++ {
- if path[i] == '|' {
- r.part = path[:i]
- r.pipe = path[i+1:]
- r.piped = true
- return
- }
- if path[i] == '.' {
- r.part = path[:i]
- r.path = path[i+1:]
- r.more = true
- return
- }
- if path[i] == '#' {
- r.arrch = true
- if i == 0 && len(path) > 1 {
- if path[1] == '.' {
- r.alogok = true
- r.alogkey = path[2:]
- r.path = path[:1]
- } 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
- }
- }
- }
- }
- continue
- }
- }
- r.part = path
- r.path = ""
- return
-}
-
-// splitQuery takes a query and splits it into three parts:
-// path, op, middle, and right.
-// So for this query:
-// #(first_name=="Murphy").last
-// Becomes
-// first_name # path
-// =="Murphy" # middle
-// .last # right
-// Or,
-// #(service_roles.#(=="one")).cap
-// Becomes
-// service_roles.#(=="one") # path
-// # middle
-// .cap # right
-func parseQuery(query string) (
- path, op, value, remain string, i int, ok bool,
-) {
- if len(query) < 2 || query[0] != '#' ||
- (query[1] != '(' && query[1] != '[') {
- return "", "", "", "", i, false
- }
- i = 2
- j := 0 // start of value part
- depth := 1
- for ; i < len(query); i++ {
- if depth == 1 && j == 0 {
- switch query[i] {
- case '!', '=', '<', '>', '%':
- // start of the value part
- j = i
- continue
- }
- }
- if query[i] == '\\' {
- i++
- } else if query[i] == '[' || query[i] == '(' {
- depth++
- } else if query[i] == ']' || query[i] == ')' {
- depth--
- if depth == 0 {
- break
- }
- } else if query[i] == '"' {
- // inside selector string, balance quotes
- i++
- for ; i < len(query); i++ {
- if query[i] == '\\' {
- i++
- } else if query[i] == '"' {
- break
- }
- }
- }
- }
- if depth > 0 {
- return "", "", "", "", i, false
- }
- if j > 0 {
- path = trim(query[2:j])
- value = trim(query[j:i])
- remain = query[i+1:]
- // parse the compare op from the value
- var opsz int
- switch {
- case len(value) == 1:
- opsz = 1
- case value[0] == '!' && value[1] == '=':
- opsz = 2
- case value[0] == '!' && value[1] == '%':
- opsz = 2
- case value[0] == '<' && value[1] == '=':
- opsz = 2
- case value[0] == '>' && value[1] == '=':
- opsz = 2
- case value[0] == '=' && value[1] == '=':
- value = value[1:]
- opsz = 1
- case value[0] == '<':
- opsz = 1
- case value[0] == '>':
- opsz = 1
- case value[0] == '=':
- opsz = 1
- case value[0] == '%':
- opsz = 1
- }
- op = value[:opsz]
- value = trim(value[opsz:])
- } else {
- path = trim(query[2:i])
- remain = query[i+1:]
- }
- return path, op, value, remain, i + 1, true
-}
-
-func trim(s string) string {
-left:
- if len(s) > 0 && s[0] <= ' ' {
- s = s[1:]
- goto left
- }
-right:
- if len(s) > 0 && s[len(s)-1] <= ' ' {
- s = s[:len(s)-1]
- goto right
- }
- return s
-}
-
-type objectPathResult struct {
- part string
- path string
- pipe string
- piped bool
- wild bool
- more bool
-}
-
-func parseObjectPath(path string) (r objectPathResult) {
- for i := 0; i < len(path); i++ {
- if path[i] == '|' {
- r.part = path[:i]
- r.pipe = path[i+1:]
- r.piped = true
- 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] == '{') {
- r.pipe = path[i+1:]
- r.piped = true
- } else {
- r.path = path[i+1:]
- r.more = true
- }
- return
- }
- if path[i] == '*' || path[i] == '?' {
- r.wild = true
- continue
- }
- if path[i] == '\\' {
- // go into escape mode. this is a slower path that
- // strips off the escape character from the part.
- epart := []byte(path[:i])
- i++
- if i < len(path) {
- epart = append(epart, path[i])
- i++
- for ; i < len(path); i++ {
- if path[i] == '\\' {
- i++
- if i < len(path) {
- epart = append(epart, path[i])
- }
- 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] == '@' {
- 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)
- r.pipe = path[i+1:]
- r.piped = true
- return
- } else if path[i] == '*' || path[i] == '?' {
- r.wild = true
- }
- epart = append(epart, path[i])
- }
- }
- // append the last part
- r.part = string(epart)
- return
- }
- }
- r.part = path
- return
-}
-
-func parseSquash(json string, i int) (int, string) {
- // expects that the lead character is a '[' or '{' or '('
- // squash the value, ignoring all nested arrays and objects.
- // the first '[' or '{' or '(' has already been read
- s := i
- i++
- depth := 1
- for ; i < len(json); i++ {
- if json[i] >= '"' && json[i] <= '}' {
- switch json[i] {
- case '"':
- i++
- s2 := i
- for ; i < len(json); i++ {
- if json[i] > '\\' {
- continue
- }
- if json[i] == '"' {
- // look for an escaped slash
- if json[i-1] == '\\' {
- n := 0
- for j := i - 2; j > s2-1; j-- {
- if json[j] != '\\' {
- break
- }
- n++
- }
- if n%2 == 0 {
- continue
- }
- }
- break
- }
- }
- case '{', '[', '(':
- depth++
- case '}', ']', ')':
- depth--
- if depth == 0 {
- i++
- return i, json[s:i]
- }
- }
- }
- }
- return i, json[s:]
-}
-
-func parseObject(c *parseContext, i int, path string) (int, bool) {
- var pmatch, kesc, vesc, ok, hit bool
- var key, val string
- rp := parseObjectPath(path)
- if !rp.more && rp.piped {
- c.pipe = rp.pipe
- c.piped = true
- }
- for i < len(c.json) {
- for ; i < len(c.json); i++ {
- if c.json[i] == '"' {
- // parse_key_string
- // this is slightly different from getting s string value
- // because we don't need the outer quotes.
- i++
- var s = i
- for ; i < len(c.json); i++ {
- if c.json[i] > '\\' {
- continue
- }
- if c.json[i] == '"' {
- i, key, kesc, ok = i+1, c.json[s:i], false, true
- goto parse_key_string_done
- }
- if c.json[i] == '\\' {
- i++
- for ; i < len(c.json); i++ {
- if c.json[i] > '\\' {
- continue
- }
- if c.json[i] == '"' {
- // look for an escaped slash
- if c.json[i-1] == '\\' {
- n := 0
- for j := i - 2; j > 0; j-- {
- if c.json[j] != '\\' {
- break
- }
- n++
- }
- if n%2 == 0 {
- continue
- }
- }
- i, key, kesc, ok = i+1, c.json[s:i], true, true
- goto parse_key_string_done
- }
- }
- break
- }
- }
- key, kesc, ok = c.json[s:], false, false
- parse_key_string_done:
- break
- }
- if c.json[i] == '}' {
- return i + 1, false
- }
- }
- if !ok {
- return i, false
- }
- if rp.wild {
- if kesc {
- pmatch = match.Match(unescape(key), rp.part)
- } else {
- pmatch = match.Match(key, rp.part)
- }
- } else {
- if kesc {
- pmatch = rp.part == unescape(key)
- } else {
- pmatch = rp.part == key
- }
- }
- hit = pmatch && !rp.more
- for ; i < len(c.json); i++ {
- switch c.json[i] {
- default:
- continue
- case '"':
- i++
- i, val, vesc, ok = parseString(c.json, i)
- if !ok {
- return i, false
- }
- if hit {
- if vesc {
- c.value.Str = unescape(val[1 : len(val)-1])
- } else {
- c.value.Str = val[1 : len(val)-1]
- }
- c.value.Raw = val
- c.value.Type = String
- return i, true
- }
- case '{':
- if pmatch && !hit {
- i, hit = parseObject(c, i+1, rp.path)
- if hit {
- return i, true
- }
- } else {
- i, val = parseSquash(c.json, i)
- if hit {
- c.value.Raw = val
- c.value.Type = JSON
- return i, true
- }
- }
- case '[':
- if pmatch && !hit {
- i, hit = parseArray(c, i+1, rp.path)
- if hit {
- return i, true
- }
- } else {
- i, val = parseSquash(c.json, i)
- if hit {
- c.value.Raw = val
- c.value.Type = JSON
- 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 't', 'f', 'n':
- vc := c.json[i]
- i, val = parseLiteral(c.json, i)
- if hit {
- c.value.Raw = val
- switch vc {
- case 't':
- c.value.Type = True
- case 'f':
- c.value.Type = False
- }
- return i, true
- }
- }
- break
- }
- }
- return i, false
-}
-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 !value.Exists() {
- return false
- }
- if rp.query.op == "" {
- // the query is only looking for existence, such as:
- // friends.#(name)
- // which makes sure that the array "friends" has an element of
- // "name" that exists
- return true
- }
- switch value.Type {
- case String:
- switch rp.query.op {
- case "=":
- return value.Str == rpv
- case "!=":
- return value.Str != rpv
- case "<":
- return value.Str < rpv
- case "<=":
- return value.Str <= rpv
- case ">":
- return value.Str > rpv
- case ">=":
- return value.Str >= rpv
- case "%":
- return match.Match(value.Str, rpv)
- case "!%":
- return !match.Match(value.Str, rpv)
- }
- case Number:
- rpvn, _ := strconv.ParseFloat(rpv, 64)
- switch rp.query.op {
- case "=":
- return value.Num == rpvn
- case "!=":
- return value.Num != rpvn
- case "<":
- return value.Num < rpvn
- case "<=":
- return value.Num <= rpvn
- case ">":
- return value.Num > rpvn
- case ">=":
- return value.Num >= rpvn
- }
- case True:
- switch rp.query.op {
- case "=":
- return rpv == "true"
- case "!=":
- return rpv != "true"
- case ">":
- return rpv == "false"
- case ">=":
- return true
- }
- case False:
- switch rp.query.op {
- case "=":
- return rpv == "false"
- case "!=":
- return rpv != "false"
- case "<":
- return rpv == "true"
- case "<=":
- return true
- }
- }
- return false
-}
-func parseArray(c *parseContext, i int, path string) (int, bool) {
- var pmatch, vesc, ok, hit bool
- var val string
- var h int
- var alog []int
- var partidx int
- var multires []byte
- rp := parseArrayPath(path)
- if !rp.arrch {
- n, ok := parseUint(rp.part)
- if !ok {
- partidx = -1
- } else {
- partidx = int(n)
- }
- }
- if !rp.more && rp.piped {
- c.pipe = rp.pipe
- c.piped = true
- }
-
- procQuery := func(qval Result) bool {
- if rp.query.all {
- if len(multires) == 0 {
- multires = append(multires, '[')
- }
- }
- var res Result
- if qval.Type == JSON {
- res = qval.Get(rp.query.path)
- } else {
- if rp.query.path != "" {
- return false
- }
- res = qval
- }
- if queryMatches(&rp, res) {
- if rp.more {
- left, right, ok := splitPossiblePipe(rp.path)
- if ok {
- rp.path = left
- c.pipe = right
- c.piped = true
- }
- res = qval.Get(rp.path)
- } else {
- res = qval
- }
- if rp.query.all {
- raw := res.Raw
- if len(raw) == 0 {
- raw = res.String()
- }
- if raw != "" {
- if len(multires) > 1 {
- multires = append(multires, ',')
- }
- multires = append(multires, raw...)
- }
- } else {
- c.value = res
- return true
- }
- }
- return false
- }
-
- for i < len(c.json)+1 {
- if !rp.arrch {
- pmatch = partidx == h
- hit = pmatch && !rp.more
- }
- h++
- if rp.alogok {
- alog = append(alog, i)
- }
- for ; ; i++ {
- var ch byte
- if i > len(c.json) {
- break
- } else if i == len(c.json) {
- ch = ']'
- } else {
- ch = c.json[i]
- }
- switch ch {
- default:
- continue
- case '"':
- i++
- i, val, vesc, ok = parseString(c.json, i)
- if !ok {
- return i, false
- }
- if rp.query.on {
- var qval Result
- if vesc {
- qval.Str = unescape(val[1 : len(val)-1])
- } else {
- qval.Str = val[1 : len(val)-1]
- }
- qval.Raw = val
- qval.Type = String
- if procQuery(qval) {
- return i, true
- }
- } else if hit {
- if rp.alogok {
- break
- }
- if vesc {
- c.value.Str = unescape(val[1 : len(val)-1])
- } else {
- c.value.Str = val[1 : len(val)-1]
- }
- c.value.Raw = val
- c.value.Type = String
- return i, true
- }
- case '{':
- if pmatch && !hit {
- i, hit = parseObject(c, i+1, rp.path)
- if hit {
- if rp.alogok {
- break
- }
- return i, true
- }
- } else {
- i, val = parseSquash(c.json, i)
- if rp.query.on {
- if procQuery(Result{Raw: val, Type: JSON}) {
- return i, true
- }
- } else if hit {
- if rp.alogok {
- break
- }
- c.value.Raw = val
- c.value.Type = JSON
- return i, true
- }
- }
- case '[':
- if pmatch && !hit {
- i, hit = parseArray(c, i+1, rp.path)
- if hit {
- if rp.alogok {
- break
- }
- return i, true
- }
- } else {
- i, val = parseSquash(c.json, i)
- if rp.query.on {
- if procQuery(Result{Raw: val, Type: JSON}) {
- return i, true
- }
- } else if hit {
- if rp.alogok {
- break
- }
- c.value.Raw = val
- c.value.Type = JSON
- 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 't', 'f', 'n':
- vc := c.json[i]
- i, val = parseLiteral(c.json, i)
- if rp.query.on {
- var qval Result
- qval.Raw = val
- switch vc {
- case 't':
- qval.Type = True
- case 'f':
- qval.Type = False
- }
- if procQuery(qval) {
- return i, true
- }
- } else if hit {
- if rp.alogok {
- break
- }
- c.value.Raw = val
- switch vc {
- case 't':
- c.value.Type = True
- case 'f':
- c.value.Type = False
- }
- return i, true
- }
- case ']':
- if rp.arrch && rp.part == "#" {
- if rp.alogok {
- left, right, ok := splitPossiblePipe(rp.alogkey)
- if ok {
- rp.alogkey = left
- c.pipe = right
- c.piped = true
- }
- var jsons = make([]byte, 0, 64)
- jsons = append(jsons, '[')
- for j, k := 0, 0; j < len(alog); j++ {
- idx := alog[j]
- for idx < len(c.json) {
- switch c.json[idx] {
- case ' ', '\t', '\r', '\n':
- idx++
- continue
- }
- break
- }
- if idx < len(c.json) && c.json[idx] != ']' {
- _, res, ok := parseAny(c.json, idx, true)
- if ok {
- res := res.Get(rp.alogkey)
- if res.Exists() {
- if k > 0 {
- jsons = append(jsons, ',')
- }
- raw := res.Raw
- if len(raw) == 0 {
- raw = res.String()
- }
- jsons = append(jsons, []byte(raw)...)
- k++
- }
- }
- }
- }
- jsons = append(jsons, ']')
- c.value.Type = JSON
- c.value.Raw = string(jsons)
- return i + 1, true
- }
- if rp.alogok {
- break
- }
-
- c.value.Type = Number
- c.value.Num = float64(h - 1)
- c.value.Raw = strconv.Itoa(h - 1)
- c.calcd = true
- return i + 1, true
- }
- if len(multires) > 0 && !c.value.Exists() {
- c.value = Result{
- Raw: string(append(multires, ']')),
- Type: JSON,
- }
- }
- return i + 1, false
- }
- break
- }
- }
- return i, false
-}
-
-func splitPossiblePipe(path string) (left, right string, ok bool) {
- // take a quick peek for the pipe character. If found we'll split the piped
- // part of the path into the c.pipe field and shorten the rp.
- var possible bool
- for i := 0; i < len(path); i++ {
- if path[i] == '|' {
- possible = true
- break
- }
- }
- if !possible {
- return
- }
-
- if len(path) > 0 && path[0] == '{' {
- squashed := squash(path[1:])
- if len(squashed) < len(path)-1 {
- squashed = path[:len(squashed)+1]
- remain := path[len(squashed):]
- if remain[0] == '|' {
- return squashed, remain[1:], true
- }
- }
- return
- }
-
- // split the left and right side of the path with the pipe character as
- // the delimiter. This is a little tricky because we'll need to basically
- // parse the entire path.
- for i := 0; i < len(path); i++ {
- if path[i] == '\\' {
- i++
- } else if path[i] == '.' {
- if i == len(path)-1 {
- return
- }
- if path[i+1] == '#' {
- i += 2
- if i == len(path) {
- return
- }
- if path[i] == '[' || path[i] == '(' {
- var start, end byte
- if path[i] == '[' {
- start, end = '[', ']'
- } else {
- start, end = '(', ')'
- }
- // inside selector, balance brackets
- i++
- depth := 1
- for ; i < len(path); i++ {
- if path[i] == '\\' {
- i++
- } else if path[i] == start {
- depth++
- } else if path[i] == end {
- depth--
- if depth == 0 {
- break
- }
- } else if path[i] == '"' {
- // inside selector string, balance quotes
- i++
- for ; i < len(path); i++ {
- if path[i] == '\\' {
- i++
- } else if path[i] == '"' {
- break
- }
- }
- }
- }
- }
- }
- } else if path[i] == '|' {
- return path[:i], path[i+1:], true
- }
- }
- return
-}
-
-// ForEachLine iterates through lines of JSON as specified by the JSON Lines
-// format (http://jsonlines.org/).
-// Each line is returned as a GJSON Result.
-func ForEachLine(json string, iterator func(line Result) bool) {
- var res Result
- var i int
- for {
- i, res, _ = parseAny(json, i, true)
- if !res.Exists() {
- break
- }
- if !iterator(res) {
- return
- }
- }
-}
-
-type subSelector struct {
- name string
- path string
-}
-
-// parseSubSelectors returns the subselectors belonging to a '[path1,path2]' or
-// '{"field1":path1,"field2":path2}' type subSelection. It's expected that the
-// first character in path is either '[' or '{', and has already been checked
-// prior to calling this function.
-func parseSubSelectors(path string) (sels []subSelector, out string, ok bool) {
- modifer := 0
- depth := 1
- colon := 0
- start := 1
- i := 1
- pushSel := func() {
- var sel subSelector
- if colon == 0 {
- sel.path = path[start:i]
- } else {
- sel.name = path[start:colon]
- sel.path = path[colon+1 : i]
- }
- sels = append(sels, sel)
- colon = 0
- start = i + 1
- }
- for ; i < len(path); i++ {
- switch path[i] {
- case '\\':
- i++
- case '@':
- if modifer == 0 && i > 0 && (path[i-1] == '.' || path[i-1] == '|') {
- modifer = i
- }
- case ':':
- if modifer == 0 && colon == 0 && depth == 1 {
- colon = i
- }
- case ',':
- if depth == 1 {
- pushSel()
- }
- case '"':
- i++
- loop:
- for ; i < len(path); i++ {
- switch path[i] {
- case '\\':
- i++
- case '"':
- break loop
- }
- }
- case '[', '(', '{':
- depth++
- case ']', ')', '}':
- depth--
- if depth == 0 {
- pushSel()
- path = path[i+1:]
- return sels, path, true
- }
- }
- }
- return
-}
-
-// nameOfLast returns the name of the last component
-func nameOfLast(path string) string {
- for i := len(path) - 1; i >= 0; i-- {
- if path[i] == '|' || path[i] == '.' {
- if i > 0 {
- if path[i-1] == '\\' {
- continue
- }
- }
- return path[i+1:]
- }
- }
- return path
-}
-
-func isSimpleName(component string) bool {
- for i := 0; i < len(component); i++ {
- if component[i] < ' ' {
- return false
- }
- switch component[i] {
- case '[', ']', '{', '}', '(', ')', '#', '|':
- return false
- }
- }
- return true
-}
-
-func appendJSONString(dst []byte, s string) []byte {
- for i := 0; i < len(s); i++ {
- if s[i] < ' ' || s[i] == '\\' || s[i] == '"' || s[i] > 126 {
- d, _ := json.Marshal(s)
- return append(dst, string(d)...)
- }
- }
- dst = append(dst, '"')
- dst = append(dst, s...)
- dst = append(dst, '"')
- return dst
-}
-
-type parseContext struct {
- json string
- value Result
- pipe string
- piped bool
- calcd bool
- lines bool
-}
-
-// 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.
-//
-// A path is a series of keys searated by a dot.
-// A key may contain special wildcard characters '*' and '?'.
-// To access an array value use the index as the key.
-// To get the number of elements in an array or to access a child path, use
-// the '#' character.
-// The dot and wildcard character can be escaped with '\'.
-//
-// {
-// "name": {"first": "Tom", "last": "Anderson"},
-// "age":37,
-// "children": ["Sara","Alex","Jack"],
-// "friends": [
-// {"first": "James", "last": "Murphy"},
-// {"first": "Roger", "last": "Craig"}
-// ]
-// }
-// "name.last" >> "Anderson"
-// "age" >> 37
-// "children" >> ["Sara","Alex","Jack"]
-// "children.#" >> 3
-// "children.1" >> "Alex"
-// "child*.2" >> "Jack"
-// "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.
-// 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 {
- if len(path) > 1 {
- if !DisableModifiers {
- if path[0] == '@' {
- // possible modifier
- var ok bool
- var npath string
- var rjson string
- 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)
- }
- }
- }
- if path[0] == '[' || path[0] == '{' {
- // using a subselector path
- kind := path[0]
- var ok bool
- var subs []subSelector
- subs, path, ok = parseSubSelectors(path)
- if ok {
- if len(path) == 0 || (path[0] == '|' || path[0] == '.') {
- var b []byte
- b = append(b, kind)
- var i int
- for _, sub := range subs {
- res := Get(json, sub.path)
- if res.Exists() {
- if i > 0 {
- b = append(b, ',')
- }
- if kind == '{' {
- if len(sub.name) > 0 {
- if sub.name[0] == '"' && Valid(sub.name) {
- b = append(b, sub.name...)
- } else {
- b = appendJSONString(b, sub.name)
- }
- } else {
- last := nameOfLast(sub.path)
- if isSimpleName(last) {
- b = appendJSONString(b, last)
- } else {
- b = appendJSONString(b, "_")
- }
- }
- b = append(b, ':')
- }
- var raw string
- if len(res.Raw) == 0 {
- raw = res.String()
- if len(raw) == 0 {
- raw = "null"
- }
- } else {
- raw = res.Raw
- }
- b = append(b, raw...)
- i++
- }
- }
- b = append(b, kind+2)
- var res Result
- res.Raw = string(b)
- res.Type = JSON
- if len(path) > 0 {
- res = res.Get(path[1:])
- }
- res.Index = 0
- return res
- }
- }
- }
- }
-
- var i int
- var c = &parseContext{json: json}
- if len(path) >= 2 && path[0] == '.' && path[1] == '.' {
- c.lines = true
- parseArray(c, 0, path[2:])
- } else {
- for ; i < len(c.json); i++ {
- if c.json[i] == '{' {
- i++
- parseObject(c, i, path)
- break
- }
- if c.json[i] == '[' {
- i++
- parseArray(c, i, path)
- break
- }
- }
- }
- if c.piped {
- res := c.value.Get(c.pipe)
- res.Index = 0
- return res
- }
- fillIndex(json, c)
- return c.value
-}
-
-// 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
-func runeit(json string) rune {
- n, _ := strconv.ParseUint(json[:4], 16, 64)
- return rune(n)
-}
-
-// unescape unescapes a string
-func unescape(json string) string {
- var str = make([]byte, 0, len(json))
- for i := 0; i < len(json); i++ {
- switch {
- default:
- str = append(str, json[i])
- case json[i] < ' ':
- return string(str)
- case json[i] == '\\':
- i++
- if i >= len(json) {
- return string(str)
- }
- switch json[i] {
- default:
- return string(str)
- case '\\':
- str = append(str, '\\')
- case '/':
- str = append(str, '/')
- case 'b':
- str = append(str, '\b')
- case 'f':
- str = append(str, '\f')
- case 'n':
- str = append(str, '\n')
- case 'r':
- str = append(str, '\r')
- case 't':
- str = append(str, '\t')
- case '"':
- str = append(str, '"')
- case 'u':
- if i+5 > len(json) {
- return string(str)
- }
- r := runeit(json[i+1:])
- i += 5
- if utf16.IsSurrogate(r) {
- // need another code
- if len(json[i:]) >= 6 && json[i] == '\\' &&
- json[i+1] == 'u' {
- // we expect it to be correct so just consume it
- r = utf16.DecodeRune(r, runeit(json[i+2:]))
- i += 6
- }
- }
- // provide enough space to encode the largest utf8 possible
- str = append(str, 0, 0, 0, 0, 0, 0, 0, 0)
- n := utf8.EncodeRune(str[len(str)-8:], r)
- str = str[:len(str)-8+n]
- i-- // backtrack index by one
- }
- }
- }
- return string(str)
-}
-
-// Less return true if a token is less than another token.
-// The caseSensitive paramater is used when the tokens are Strings.
-// The order when comparing two different type is:
-//
-// Null < False < Number < String < True < JSON
-//
-func (t Result) Less(token Result, caseSensitive bool) bool {
- if t.Type < token.Type {
- return true
- }
- if t.Type > token.Type {
- return false
- }
- if t.Type == String {
- if caseSensitive {
- return t.Str < token.Str
- }
- return stringLessInsensitive(t.Str, token.Str)
- }
- if t.Type == Number {
- return t.Num < token.Num
- }
- return t.Raw < token.Raw
-}
-
-func stringLessInsensitive(a, b string) bool {
- for i := 0; i < len(a) && i < len(b); i++ {
- if a[i] >= 'A' && a[i] <= 'Z' {
- if b[i] >= 'A' && b[i] <= 'Z' {
- // both are uppercase, do nothing
- if a[i] < b[i] {
- return true
- } else if a[i] > b[i] {
- return false
- }
- } else {
- // a is uppercase, convert a to lowercase
- if a[i]+32 < b[i] {
- return true
- } else if a[i]+32 > b[i] {
- return false
- }
- }
- } else if b[i] >= 'A' && b[i] <= 'Z' {
- // b is uppercase, convert b to lowercase
- if a[i] < b[i]+32 {
- return true
- } else if a[i] > b[i]+32 {
- return false
- }
- } else {
- // neither are uppercase
- if a[i] < b[i] {
- return true
- } else if a[i] > b[i] {
- return false
- }
- }
- }
- return len(a) < len(b)
-}
-
-// parseAny parses the next value from a json string.
-// A Result is returned when the hit param is set.
-// The return values are (i int, res Result, ok bool)
-func parseAny(json string, i int, hit bool) (int, Result, bool) {
- var res Result
- var val string
- for ; i < len(json); i++ {
- if json[i] == '{' || json[i] == '[' {
- i, val = parseSquash(json, i)
- if hit {
- res.Raw = val
- res.Type = JSON
- }
- return i, res, true
- }
- if json[i] <= ' ' {
- continue
- }
- switch json[i] {
- case '"':
- i++
- var vesc bool
- var ok bool
- i, val, vesc, ok = parseString(json, i)
- if !ok {
- return i, res, false
- }
- if hit {
- res.Type = String
- res.Raw = val
- if vesc {
- res.Str = unescape(val[1 : len(val)-1])
- } else {
- res.Str = val[1 : len(val)-1]
- }
- }
- return i, res, true
- case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
- i, val = parseNumber(json, i)
- if hit {
- res.Raw = val
- res.Type = Number
- res.Num, _ = strconv.ParseFloat(val, 64)
- }
- return i, res, true
- case 't', 'f', 'n':
- vc := json[i]
- i, val = parseLiteral(json, i)
- if hit {
- res.Raw = val
- switch vc {
- case 't':
- res.Type = True
- case 'f':
- res.Type = False
- }
- return i, res, true
- }
- }
- }
- return i, res, false
-}
-
-var ( // used for testing
- testWatchForFallback bool
- testLastWasFallback bool
-)
-
-// GetMany searches json for the multiple paths.
-// The return value is a Result array where the number of items
-// will be equal to the number of input paths.
-func GetMany(json string, path ...string) []Result {
- res := make([]Result, len(path))
- for i, path := range path {
- res[i] = Get(json, path)
- }
- return res
-}
-
-// 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 {
- res := make([]Result, len(path))
- for i, path := range path {
- res[i] = GetBytes(json, path)
- }
- return res
-}
-
-func validpayload(data []byte, i int) (outi int, ok bool) {
- for ; i < len(data); i++ {
- switch data[i] {
- default:
- i, ok = validany(data, i)
- if !ok {
- return i, false
- }
- for ; i < len(data); i++ {
- switch data[i] {
- default:
- return i, false
- case ' ', '\t', '\n', '\r':
- continue
- }
- }
- return i, true
- case ' ', '\t', '\n', '\r':
- continue
- }
- }
- return i, false
-}
-func validany(data []byte, i int) (outi int, ok bool) {
- for ; i < len(data); i++ {
- switch data[i] {
- default:
- return i, false
- case ' ', '\t', '\n', '\r':
- continue
- case '{':
- return validobject(data, i+1)
- case '[':
- return validarray(data, i+1)
- case '"':
- return validstring(data, i+1)
- case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
- return validnumber(data, i+1)
- case 't':
- return validtrue(data, i+1)
- case 'f':
- return validfalse(data, i+1)
- case 'n':
- return validnull(data, i+1)
- }
- }
- return i, false
-}
-func validobject(data []byte, i int) (outi int, ok bool) {
- for ; i < len(data); i++ {
- switch data[i] {
- default:
- return i, false
- case ' ', '\t', '\n', '\r':
- continue
- case '}':
- return i + 1, true
- case '"':
- key:
- if i, ok = validstring(data, i+1); !ok {
- return i, false
- }
- if i, ok = validcolon(data, i); !ok {
- return i, false
- }
- if i, ok = validany(data, i); !ok {
- return i, false
- }
- if i, ok = validcomma(data, i, '}'); !ok {
- return i, false
- }
- if data[i] == '}' {
- return i + 1, true
- }
- i++
- for ; i < len(data); i++ {
- switch data[i] {
- default:
- return i, false
- case ' ', '\t', '\n', '\r':
- continue
- case '"':
- goto key
- }
- }
- return i, false
- }
- }
- return i, false
-}
-func validcolon(data []byte, i int) (outi int, ok bool) {
- for ; i < len(data); i++ {
- switch data[i] {
- default:
- return i, false
- case ' ', '\t', '\n', '\r':
- continue
- case ':':
- return i + 1, true
- }
- }
- return i, false
-}
-func validcomma(data []byte, i int, end byte) (outi int, ok bool) {
- for ; i < len(data); i++ {
- switch data[i] {
- default:
- return i, false
- case ' ', '\t', '\n', '\r':
- continue
- case ',':
- return i, true
- case end:
- return i, true
- }
- }
- return i, false
-}
-func validarray(data []byte, i int) (outi int, ok bool) {
- for ; i < len(data); i++ {
- switch data[i] {
- default:
- for ; i < len(data); i++ {
- if i, ok = validany(data, i); !ok {
- return i, false
- }
- if i, ok = validcomma(data, i, ']'); !ok {
- return i, false
- }
- if data[i] == ']' {
- return i + 1, true
- }
- }
- case ' ', '\t', '\n', '\r':
- continue
- case ']':
- return i + 1, true
- }
- }
- return i, false
-}
-func validstring(data []byte, i int) (outi int, ok bool) {
- for ; i < len(data); i++ {
- if data[i] < ' ' {
- return i, false
- } else if data[i] == '\\' {
- i++
- if i == len(data) {
- return i, false
- }
- switch data[i] {
- default:
- return i, false
- case '"', '\\', '/', 'b', 'f', 'n', 'r', 't':
- case 'u':
- for j := 0; j < 4; j++ {
- i++
- if i >= len(data) {
- return i, false
- }
- if !((data[i] >= '0' && data[i] <= '9') ||
- (data[i] >= 'a' && data[i] <= 'f') ||
- (data[i] >= 'A' && data[i] <= 'F')) {
- return i, false
- }
- }
- }
- } else if data[i] == '"' {
- return i + 1, true
- }
- }
- return i, false
-}
-func validnumber(data []byte, i int) (outi int, ok bool) {
- i--
- // sign
- if data[i] == '-' {
- i++
- }
- // int
- if i == len(data) {
- return i, false
- }
- if data[i] == '0' {
- i++
- } else {
- for ; i < len(data); i++ {
- if data[i] >= '0' && data[i] <= '9' {
- continue
- }
- break
- }
- }
- // frac
- if i == len(data) {
- return i, true
- }
- if data[i] == '.' {
- i++
- if i == len(data) {
- return i, false
- }
- if data[i] < '0' || data[i] > '9' {
- return i, false
- }
- i++
- for ; i < len(data); i++ {
- if data[i] >= '0' && data[i] <= '9' {
- continue
- }
- break
- }
- }
- // exp
- if i == len(data) {
- return i, true
- }
- if data[i] == 'e' || data[i] == 'E' {
- i++
- if i == len(data) {
- return i, false
- }
- if data[i] == '+' || data[i] == '-' {
- i++
- }
- if i == len(data) {
- return i, false
- }
- if data[i] < '0' || data[i] > '9' {
- return i, false
- }
- i++
- for ; i < len(data); i++ {
- if data[i] >= '0' && data[i] <= '9' {
- continue
- }
- break
- }
- }
- return i, true
-}
-
-func validtrue(data []byte, i int) (outi int, ok bool) {
- if i+3 <= len(data) && data[i] == 'r' && data[i+1] == 'u' &&
- data[i+2] == 'e' {
- return i + 3, true
- }
- 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' {
- return i + 4, true
- }
- 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' {
- return i + 3, true
- }
- return i, false
-}
-
-// Valid returns true if the input is valid json.
-//
-// if !gjson.Valid(json) {
-// return errors.New("invalid json")
-// }
-// value := gjson.Get(json, "name.last")
-//
-func Valid(json string) bool {
- _, ok := validpayload(stringBytes(json), 0)
- return ok
-}
-
-// ValidBytes returns true if the input is valid json.
-//
-// if !gjson.Valid(json) {
-// return errors.New("invalid json")
-// }
-// value := gjson.Get(json, "name.last")
-//
-// If working with bytes, this method preferred over ValidBytes(string(data))
-//
-func ValidBytes(json []byte) bool {
- _, ok := validpayload(json, 0)
- return ok
-}
-
-func parseUint(s string) (n uint64, ok bool) {
- var i int
- if i == len(s) {
- return 0, false
- }
- for ; i < len(s); i++ {
- if s[i] >= '0' && s[i] <= '9' {
- n = n*10 + uint64(s[i]-'0')
- } else {
- return 0, false
- }
- }
- return n, true
-}
-
-func parseInt(s string) (n int64, ok bool) {
- var i int
- var sign bool
- if len(s) > 0 && s[0] == '-' {
- sign = true
- i++
- }
- if i == len(s) {
- return 0, false
- }
- for ; i < len(s); i++ {
- if s[i] >= '0' && s[i] <= '9' {
- n = n*10 + int64(s[i]-'0')
- } else {
- return 0, false
- }
- }
- if sign {
- return n * -1, true
- }
- return n, true
-}
-
-const minUint53 = 0
-const maxUint53 = 4503599627370495
-const minInt53 = -2251799813685248
-const maxInt53 = 2251799813685247
-
-func floatToUint(f float64) (n uint64, ok bool) {
- n = uint64(f)
- if float64(n) == f && n >= minUint53 && n <= maxUint53 {
- return n, true
- }
- return 0, false
-}
-
-func floatToInt(f float64) (n int64, ok bool) {
- n = int64(f)
- if float64(n) == f && n >= minInt53 && n <= maxInt53 {
- return n, true
- }
- return 0, false
-}
-
-// execModifier parses the path to find a matching modifier function.
-// then input expects that the path already starts with a '@'
-func execModifier(json, path string) (pathOut, res string, ok bool) {
- name := path[1:]
- var hasArgs bool
- for i := 1; i < len(path); i++ {
- if path[i] == ':' {
- pathOut = path[i+1:]
- name = path[1:i]
- hasArgs = len(pathOut) > 0
- break
- }
- if path[i] == '|' {
- pathOut = path[i:]
- name = path[1:i]
- break
- }
- if path[i] == '.' {
- pathOut = path[i:]
- name = path[1:i]
- break
- }
- }
- if fn, ok := modifiers[name]; ok {
- var args string
- if hasArgs {
- var parsedArgs bool
- switch pathOut[0] {
- case '{', '[', '"':
- res := Parse(pathOut)
- if res.Exists() {
- args = squash(pathOut)
- pathOut = pathOut[len(args):]
- parsedArgs = true
- }
- }
- if !parsedArgs {
- idx := strings.IndexByte(pathOut, '|')
- if idx == -1 {
- args = pathOut
- pathOut = ""
- } else {
- args = pathOut[:idx]
- pathOut = pathOut[idx:]
- }
- }
- }
- return pathOut, fn(json, args), true
- }
- return pathOut, res, false
-}
-
-// unwrap removes the '[]' or '{}' characters around json
-func unwrap(json string) string {
- json = trim(json)
- if len(json) >= 2 && json[0] == '[' || json[0] == '{' {
- json = json[1 : len(json)-1]
- }
- return json
-}
-
-// DisableModifiers will disable the modifier syntax
-var DisableModifiers = false
-
-var modifiers = map[string]func(json, arg string) string{
- "pretty": modPretty,
- "ugly": modUgly,
- "reverse": modReverse,
- "this": modThis,
- "flatten": modFlatten,
- "join": modJoin,
- "valid": modValid,
-}
-
-// AddModifier binds a custom modifier command to the GJSON syntax.
-// This operation is not thread safe and should be executed prior to
-// using all other gjson function.
-func AddModifier(name string, fn func(json, arg string) string) {
- modifiers[name] = fn
-}
-
-// ModifierExists returns true when the specified modifier exists.
-func ModifierExists(name string, fn func(json, arg string) string) bool {
- _, ok := modifiers[name]
- return ok
-}
-
-// @pretty modifier makes the json look nice.
-func modPretty(json, arg string) string {
- if len(arg) > 0 {
- opts := *pretty.DefaultOptions
- Parse(arg).ForEach(func(key, value Result) bool {
- switch key.String() {
- case "sortKeys":
- opts.SortKeys = value.Bool()
- case "indent":
- opts.Indent = value.String()
- case "prefix":
- opts.Prefix = value.String()
- case "width":
- opts.Width = int(value.Int())
- }
- return true
- })
- return bytesString(pretty.PrettyOptions(stringBytes(json), &opts))
- }
- return bytesString(pretty.Pretty(stringBytes(json)))
-}
-
-// @this returns the current element. Can be used to retrieve the root element.
-func modThis(json, arg string) string {
- return json
-}
-
-// @ugly modifier removes all whitespace.
-func modUgly(json, arg string) string {
- return bytesString(pretty.Ugly(stringBytes(json)))
-}
-
-// @reverse reverses array elements or root object members.
-func modReverse(json, arg string) string {
- res := Parse(json)
- if res.IsArray() {
- var values []Result
- res.ForEach(func(_, value Result) bool {
- values = append(values, value)
- return true
- })
- out := make([]byte, 0, len(json))
- out = append(out, '[')
- for i, j := len(values)-1, 0; i >= 0; i, j = i-1, j+1 {
- if j > 0 {
- out = append(out, ',')
- }
- out = append(out, values[i].Raw...)
- }
- out = append(out, ']')
- return bytesString(out)
- }
- if res.IsObject() {
- var keyValues []Result
- res.ForEach(func(key, value Result) bool {
- keyValues = append(keyValues, key, value)
- return true
- })
- out := make([]byte, 0, len(json))
- out = append(out, '{')
- for i, j := len(keyValues)-2, 0; i >= 0; i, j = i-2, j+1 {
- if j > 0 {
- out = append(out, ',')
- }
- out = append(out, keyValues[i+0].Raw...)
- out = append(out, ':')
- out = append(out, keyValues[i+1].Raw...)
- }
- out = append(out, '}')
- return bytesString(out)
- }
- return json
-}
-
-// @flatten an array with child arrays.
-// [1,[2],[3,4],[5,[6,7]]] -> [1,2,3,4,5,[6,7]]
-// The {"deep":true} arg can be provide for deep flattening.
-// [1,[2],[3,4],[5,[6,7]]] -> [1,2,3,4,5,6,7]
-// The original json is returned when the json is not an array.
-func modFlatten(json, arg string) string {
- res := Parse(json)
- if !res.IsArray() {
- return json
- }
- var deep bool
- if arg != "" {
- Parse(arg).ForEach(func(key, value Result) bool {
- if key.String() == "deep" {
- deep = value.Bool()
- }
- return true
- })
- }
- var out []byte
- out = append(out, '[')
- var idx int
- res.ForEach(func(_, value Result) bool {
- if idx > 0 {
- out = append(out, ',')
- }
- if value.IsArray() {
- if deep {
- out = append(out, unwrap(modFlatten(value.Raw, arg))...)
- } else {
- out = append(out, unwrap(value.Raw)...)
- }
- } else {
- out = append(out, value.Raw...)
- }
- idx++
- return true
- })
- out = append(out, ']')
- return bytesString(out)
-}
-
-// @join multiple objects into a single object.
-// [{"first":"Tom"},{"last":"Smith"}] -> {"first","Tom","last":"Smith"}
-// The arg can be "true" to specify that duplicate keys should be preserved.
-// [{"first":"Tom","age":37},{"age":41}] -> {"first","Tom","age":37,"age":41}
-// Without preserved keys:
-// [{"first":"Tom","age":37},{"age":41}] -> {"first","Tom","age":41}
-// The original json is returned when the json is not an object.
-func modJoin(json, arg string) string {
- res := Parse(json)
- if !res.IsArray() {
- return json
- }
- var preserve bool
- if arg != "" {
- Parse(arg).ForEach(func(key, value Result) bool {
- if key.String() == "preserve" {
- preserve = value.Bool()
- }
- return true
- })
- }
- var out []byte
- out = append(out, '{')
- if preserve {
- // Preserve duplicate keys.
- var idx int
- res.ForEach(func(_, value Result) bool {
- if !value.IsObject() {
- return true
- }
- if idx > 0 {
- out = append(out, ',')
- }
- out = append(out, unwrap(value.Raw)...)
- idx++
- return true
- })
- } else {
- // Deduplicate keys and generate an object with stable ordering.
- var keys []Result
- kvals := make(map[string]Result)
- res.ForEach(func(_, value Result) bool {
- if !value.IsObject() {
- return true
- }
- value.ForEach(func(key, value Result) bool {
- k := key.String()
- if _, ok := kvals[k]; !ok {
- keys = append(keys, key)
- }
- kvals[k] = value
- return true
- })
- return true
- })
- for i := 0; i < len(keys); i++ {
- if i > 0 {
- out = append(out, ',')
- }
- out = append(out, keys[i].Raw...)
- out = append(out, ':')
- out = append(out, kvals[keys[i].String()].Raw...)
- }
- }
- out = append(out, '}')
- return bytesString(out)
-}
-
-// @valid ensures that the json is valid before moving on. An empty string is
-// returned when the json is not valid, otherwise it returns the original json.
-func modValid(json, arg string) string {
- if !Valid(json) {
- return ""
- }
- return json
-}
diff --git a/gjson/gjson_gae.go b/gjson/gjson_gae.go
deleted file mode 100644
index bacc11eea..000000000
--- a/gjson/gjson_gae.go
+++ /dev/null
@@ -1,34 +0,0 @@
-//+build appengine js
-
-package gjson
-
-import (
- "reflect"
- "unsafe"
-)
-
-func getBytes(json []byte, path string) Result {
- return Get(string(json), path)
-}
-
-// fillIndex finds the position of Raw data and assigns it to the Index field
-// of the resulting value. If the position cannot be found then Index zero is
-// used instead.
-func fillIndex(json string, c *parseContext) {
- if len(c.value.Raw) > 0 && !c.calcd {
- jhdr := *(*reflect.StringHeader)(unsafe.Pointer(&json))
- rhdr := *(*reflect.StringHeader)(unsafe.Pointer(&(c.value.Raw)))
- c.value.Index = int(rhdr.Data - jhdr.Data)
- if c.value.Index < 0 || c.value.Index >= len(json) {
- c.value.Index = 0
- }
- }
-}
-
-func stringBytes(s string) []byte {
- return []byte(s)
-}
-
-func bytesString(b []byte) string {
- return string(b)
-}
diff --git a/gjson/gjson_ngae.go b/gjson/gjson_ngae.go
deleted file mode 100644
index bc608b53b..000000000
--- a/gjson/gjson_ngae.go
+++ /dev/null
@@ -1,81 +0,0 @@
-//+build !appengine
-//+build !js
-
-package gjson
-
-import (
- "reflect"
- "unsafe"
-)
-
-// getBytes casts the input json bytes to a string and safely returns the
-// results as uniquely allocated data. This operation is intended to minimize
-// copies and allocations for the large json string->[]byte.
-func getBytes(json []byte, path string) Result {
- var result Result
- if json != nil {
- // unsafe cast to string
- result = Get(*(*string)(unsafe.Pointer(&json)), path)
- // safely get the string headers
- rawhi := *(*reflect.StringHeader)(unsafe.Pointer(&result.Raw))
- strhi := *(*reflect.StringHeader)(unsafe.Pointer(&result.Str))
- // create byte slice headers
- rawh := reflect.SliceHeader{Data: rawhi.Data, Len: rawhi.Len}
- strh := reflect.SliceHeader{Data: strhi.Data, Len: strhi.Len}
- if strh.Data == 0 {
- // str is nil
- if rawh.Data == 0 {
- // raw is nil
- result.Raw = ""
- } else {
- // raw has data, safely copy the slice header to a string
- result.Raw = string(*(*[]byte)(unsafe.Pointer(&rawh)))
- }
- result.Str = ""
- } else if rawh.Data == 0 {
- // raw is nil
- result.Raw = ""
- // str has data, safely copy the slice header to a string
- result.Str = string(*(*[]byte)(unsafe.Pointer(&strh)))
- } else if strh.Data >= rawh.Data &&
- int(strh.Data)+strh.Len <= int(rawh.Data)+rawh.Len {
- // Str is a substring of Raw.
- start := int(strh.Data - rawh.Data)
- // safely copy the raw slice header
- result.Raw = string(*(*[]byte)(unsafe.Pointer(&rawh)))
- // substring the raw
- result.Str = result.Raw[start : start+strh.Len]
- } else {
- // safely copy both the raw and str slice headers to strings
- result.Raw = string(*(*[]byte)(unsafe.Pointer(&rawh)))
- result.Str = string(*(*[]byte)(unsafe.Pointer(&strh)))
- }
- }
- return result
-}
-
-// fillIndex finds the position of Raw data and assigns it to the Index field
-// of the resulting value. If the position cannot be found then Index zero is
-// used instead.
-func fillIndex(json string, c *parseContext) {
- if len(c.value.Raw) > 0 && !c.calcd {
- jhdr := *(*reflect.StringHeader)(unsafe.Pointer(&json))
- rhdr := *(*reflect.StringHeader)(unsafe.Pointer(&(c.value.Raw)))
- c.value.Index = int(rhdr.Data - jhdr.Data)
- if c.value.Index < 0 || c.value.Index >= len(json) {
- c.value.Index = 0
- }
- }
-}
-
-func stringBytes(s string) []byte {
- return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
- Data: (*reflect.StringHeader)(unsafe.Pointer(&s)).Data,
- Len: len(s),
- Cap: len(s),
- }))
-}
-
-func bytesString(b []byte) string {
- return *(*string)(unsafe.Pointer(&b))
-}
diff --git a/gjson/gjson_test.go b/gjson/gjson_test.go
deleted file mode 100644
index 36a427332..000000000
--- a/gjson/gjson_test.go
+++ /dev/null
@@ -1,2164 +0,0 @@
-package gjson
-
-import (
- "bytes"
- "encoding/hex"
- "encoding/json"
- "fmt"
- "math/rand"
- "strconv"
- "strings"
- "testing"
- "time"
-
- "github.com/tidwall/pretty"
-)
-
-// TestRandomData is a fuzzing test that throws random data at the Parse
-// function looking for panics.
-func TestRandomData(t *testing.T) {
- var lstr string
- defer func() {
- if v := recover(); v != nil {
- println("'" + hex.EncodeToString([]byte(lstr)) + "'")
- println("'" + lstr + "'")
- panic(v)
- }
- }()
- rand.Seed(time.Now().UnixNano())
- b := make([]byte, 200)
- for i := 0; i < 2000000; i++ {
- n, err := rand.Read(b[:rand.Int()%len(b)])
- if err != nil {
- t.Fatal(err)
- }
- lstr = string(b[:n])
- GetBytes([]byte(lstr), "zzzz")
- Parse(lstr)
- }
-}
-
-func TestRandomValidStrings(t *testing.T) {
- rand.Seed(time.Now().UnixNano())
- b := make([]byte, 200)
- for i := 0; i < 100000; i++ {
- n, err := rand.Read(b[:rand.Int()%len(b)])
- if err != nil {
- t.Fatal(err)
- }
- sm, err := json.Marshal(string(b[:n]))
- if err != nil {
- t.Fatal(err)
- }
- var su string
- if err := json.Unmarshal([]byte(sm), &su); err != nil {
- t.Fatal(err)
- }
- token := Get(`{"str":`+string(sm)+`}`, "str")
- if token.Type != String || token.Str != su {
- println("["+token.Raw+"]", "["+token.Str+"]", "["+su+"]",
- "["+string(sm)+"]")
- t.Fatal("string mismatch")
- }
- }
-}
-
-func TestEmoji(t *testing.T) {
- 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())
- }
-}
-
-func testEscapePath(t *testing.T, json, path, expect string) {
- if Get(json, path).String() != expect {
- t.Fatalf("expected '%v', got '%v'", expect, Get(json, path).String())
- }
-}
-
-func TestEscapePath(t *testing.T) {
- json := `{
- "test":{
- "*":"valZ",
- "*v":"val0",
- "keyv*":"val1",
- "key*v":"val2",
- "keyv?":"val3",
- "key?v":"val4",
- "keyv.":"val5",
- "key.v":"val6",
- "keyk*":{"key?":"val7"}
- }
- }`
-
- testEscapePath(t, json, "test.\\*", "valZ")
- testEscapePath(t, json, "test.\\*v", "val0")
- testEscapePath(t, json, "test.keyv\\*", "val1")
- testEscapePath(t, json, "test.key\\*v", "val2")
- testEscapePath(t, json, "test.keyv\\?", "val3")
- testEscapePath(t, json, "test.key\\?v", "val4")
- testEscapePath(t, json, "test.keyv\\.", "val5")
- testEscapePath(t, json, "test.key\\.v", "val6")
- testEscapePath(t, json, "test.keyk\\*.key\\?", "val7")
-}
-
-// 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,
- "items":[1,2,3,{"tags":[1,2,3],"points":[[1,2],[3,4]]},4,5,6,7],
- "arr":["1",2,"3",{"hello":"world"},"4",5],
- "vals":[1,2,3,{"sadf":sdf"asdf"}],"name":{"first":"tom","last":null},
- "created":"2014-05-16T08:28:06.989Z",
- "loggy":{
- "programmers": [
- {
- "firstName": "Brett",
- "lastName": "McLaughlin",
- "email": "aaaa",
- "tag": "good"
- },
- {
- "firstName": "Jason",
- "lastName": "Hunter",
- "email": "bbbb",
- "tag": "bad"
- },
- {
- "firstName": "Elliotte",
- "lastName": "Harold",
- "email": "cccc",
- "tag":, "good"
- },
- {
- "firstName": 1002.3,
- "age": 101
- }
- ]
- },
- "lastly":{"yay":"final"}
-}`
-var basicJSONB = []byte(basicJSON)
-
-func TestTimeResult(t *testing.T) {
- assert(t, Get(basicJSON, "created").String() ==
- Get(basicJSON, "created").Time().Format(time.RFC3339Nano))
-}
-
-func TestParseAny(t *testing.T) {
- assert(t, Parse("100").Float() == 100)
- assert(t, Parse("true").Bool())
- assert(t, Parse("false").Bool() == false)
- assert(t, Parse("yikes").Exists() == false)
-}
-
-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}
- paths := []string{"a", "b", "c"}
- 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...)
- for i := 0; i < len(paths); i++ {
- if results[i].String() != expects[i] {
- t.Fatalf("expected '%v', got '%v'", expects[i],
- results[i].String())
- }
- }
- }
-}
-func TestManyRecursion(t *testing.T) {
- var json string
- var path string
- for i := 0; i < 100; i++ {
- json += `{"a":`
- path += ".a"
- }
- json += `"b"`
- for i := 0; i < 100; i++ {
- json += `}`
- }
- 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")
- if mtok.String() != "Janet" {
- t.Fatalf("expected %v, got %v", "Jason", mtok.String())
- }
- mtok2 := GetBytes(jsonb, "age")
- if mtok2.Raw != "38" {
- t.Fatalf("expected %v, got %v", "Jason", mtok2.Raw)
- }
- jsonb[9] = 'T'
- jsonb[12] = 'd'
- jsonb[13] = 'y'
- if mtok.String() != "Janet" {
- t.Fatalf("expected %v, got %v", "Jason", mtok.String())
- }
-}
-
-func get(json, path string) Result {
- return GetBytes([]byte(json), path)
-}
-
-func TestBasic(t *testing.T) {
- var mtok Result
- mtok = get(basicJSON, `loggy.programmers.#[tag="good"].firstName`)
- if mtok.String() != "Brett" {
- t.Fatalf("expected %v, got %v", "Brett", mtok.String())
- }
- mtok = get(basicJSON, `loggy.programmers.#[tag="good"]#.firstName`)
- if mtok.String() != `["Brett","Elliotte"]` {
- t.Fatalf("expected %v, got %v", `["Brett","Elliotte"]`, mtok.String())
- }
-}
-
-func TestIsArrayIsObject(t *testing.T) {
- mtok := get(basicJSON, "loggy")
- assert(t, mtok.IsObject())
- assert(t, !mtok.IsArray())
-
- mtok = get(basicJSON, "loggy.programmers")
- assert(t, !mtok.IsObject())
- assert(t, mtok.IsArray())
-
- mtok = get(basicJSON, `loggy.programmers.#[tag="good"]#.firstName`)
- assert(t, mtok.IsArray())
-
- mtok = get(basicJSON, `loggy.programmers.0.firstName`)
- assert(t, !mtok.IsObject())
- assert(t, !mtok.IsArray())
-}
-
-func TestPlus53BitInts(t *testing.T) {
- json := `{"IdentityData":{"GameInstanceId":634866135153775564}}`
- value := Get(json, "IdentityData.GameInstanceId")
- assert(t, value.Uint() == 634866135153775564)
- assert(t, value.Int() == 634866135153775564)
- assert(t, value.Float() == 634866135153775616)
-
- json = `{"IdentityData":{"GameInstanceId":634866135153775564.88172}}`
- value = Get(json, "IdentityData.GameInstanceId")
- assert(t, value.Uint() == 634866135153775616)
- assert(t, value.Int() == 634866135153775616)
- assert(t, value.Float() == 634866135153775616.88172)
-
- json = `{
- "min_uint64": 0,
- "max_uint64": 18446744073709551615,
- "overflow_uint64": 18446744073709551616,
- "min_int64": -9223372036854775808,
- "max_int64": 9223372036854775807,
- "overflow_int64": 9223372036854775808,
- "min_uint53": 0,
- "max_uint53": 4503599627370495,
- "overflow_uint53": 4503599627370496,
- "min_int53": -2251799813685248,
- "max_int53": 2251799813685247,
- "overflow_int53": 2251799813685248
- }`
-
- assert(t, Get(json, "min_uint53").Uint() == 0)
- assert(t, Get(json, "max_uint53").Uint() == 4503599627370495)
- assert(t, Get(json, "overflow_uint53").Int() == 4503599627370496)
- assert(t, Get(json, "min_int53").Int() == -2251799813685248)
- assert(t, Get(json, "max_int53").Int() == 2251799813685247)
- assert(t, Get(json, "overflow_int53").Int() == 2251799813685248)
- assert(t, Get(json, "min_uint64").Uint() == 0)
- assert(t, Get(json, "max_uint64").Uint() == 18446744073709551615)
- // this next value overflows the max uint64 by one which will just
- // flip the number to zero
- assert(t, Get(json, "overflow_uint64").Int() == 0)
- assert(t, Get(json, "min_int64").Int() == -9223372036854775808)
- assert(t, Get(json, "max_int64").Int() == 9223372036854775807)
- // this next value overflows the max int64 by one which will just
- // 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.
- Get(`["S3O PEDRO DO BUTI\udf93"]`, "0")
- Get(`["S3O PEDRO DO BUTI\udf93asdf"]`, "0")
- Get(`["S3O PEDRO DO BUTI\udf93\u"]`, "0")
- Get(`["S3O PEDRO DO BUTI\udf93\u1"]`, "0")
- Get(`["S3O PEDRO DO BUTI\udf93\u13"]`, "0")
- Get(`["S3O PEDRO DO BUTI\udf93\u134"]`, "0")
- 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")
- assert(t, (Result{Type: Null}).Type.String() == "Null")
- assert(t, (Result{Type: False}).Type.String() == "False")
- assert(t, (Result{Type: True}).Type.String() == "True")
- assert(t, (Result{Type: JSON}).Type.String() == "JSON")
- assert(t, (Result{Type: 100}).Type.String() == "")
- // bool
- assert(t, (Result{Type: String, Str: "true"}).Bool())
- assert(t, (Result{Type: True}).Bool())
- assert(t, (Result{Type: False}).Bool() == false)
- assert(t, (Result{Type: Number, Num: 1}).Bool())
- // int
- assert(t, (Result{Type: String, Str: "1"}).Int() == 1)
- assert(t, (Result{Type: True}).Int() == 1)
- assert(t, (Result{Type: False}).Int() == 0)
- assert(t, (Result{Type: Number, Num: 1}).Int() == 1)
- // uint
- assert(t, (Result{Type: String, Str: "1"}).Uint() == 1)
- assert(t, (Result{Type: True}).Uint() == 1)
- assert(t, (Result{Type: False}).Uint() == 0)
- assert(t, (Result{Type: Number, Num: 1}).Uint() == 1)
- // float
- assert(t, (Result{Type: String, Str: "1"}).Float() == 1)
- assert(t, (Result{Type: True}).Float() == 1)
- 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 {
- assert(t, value.String() == "Hello")
- return false
- })
- Result{Type: JSON, Raw: "*invalid*"}.ForEach(nil)
-
- json := ` {"name": {"first": "Janet","last": "Prichard"},
- "asd\nf":"\ud83d\udd13","age": 47}`
- var count int
- ParseBytes([]byte(json)).ForEach(func(key, value Result) bool {
- count++
- return true
- })
- assert(t, count == 3)
- 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() ==
- "ghjk")
- assert(t, len(Result{Type: JSON, Raw: "**invalid**"}.Map()) == 0)
- 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
- mtok.ForEach(func(key, value Result) bool {
- if key.Exists() {
- t.Fatalf("expected %v, got %v", false, key.Exists())
- }
- count++
- if count == 3 {
- return false
- }
- if count == 1 {
- i := 0
- value.ForEach(func(key, value Result) bool {
- switch i {
- case 0:
- if key.String() != "firstName" ||
- value.String() != "Brett" {
- t.Fatalf("expected %v/%v got %v/%v", "firstName",
- "Brett", key.String(), value.String())
- }
- case 1:
- if key.String() != "lastName" ||
- value.String() != "McLaughlin" {
- t.Fatalf("expected %v/%v got %v/%v", "lastName",
- "McLaughlin", key.String(), value.String())
- }
- case 2:
- if key.String() != "email" || value.String() != "aaaa" {
- t.Fatalf("expected %v/%v got %v/%v", "email", "aaaa",
- key.String(), value.String())
- }
- }
- i++
- return true
- })
- }
- return true
- })
- if count != 3 {
- 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" {
- t.Fatalf("expected %v, got %v", "1002.3", mtok.String())
- }
- mtok = get(basicJSON,
- `loggy.programmers.#[firstName != "Brett"].firstName`)
- if mtok.String() != "Jason" {
- t.Fatalf("expected %v, got %v", "Jason", mtok.String())
- }
- mtok = get(basicJSON, `loggy.programmers.#[firstName % "Bre*"].email`)
- if mtok.String() != "aaaa" {
- t.Fatalf("expected %v, got %v", "aaaa", mtok.String())
- }
- mtok = get(basicJSON, `loggy.programmers.#[firstName !% "Bre*"].email`)
- if mtok.String() != "bbbb" {
- t.Fatalf("expected %v, got %v", "bbbb", mtok.String())
- }
- mtok = get(basicJSON, `loggy.programmers.#[firstName == "Brett"].email`)
- if mtok.String() != "aaaa" {
- t.Fatalf("expected %v, got %v", "aaaa", mtok.String())
- }
- mtok = get(basicJSON, "loggy")
- if mtok.Type != JSON {
- t.Fatalf("expected %v, got %v", JSON, mtok.Type)
- }
- if len(mtok.Map()) != 1 {
- t.Fatalf("expected %v, got %v", 1, len(mtok.Map()))
- }
- programmers := mtok.Map()["programmers"]
- if programmers.Array()[1].Map()["firstName"].Str != "Jason" {
- t.Fatalf("expected %v, got %v", "Jason",
- mtok.Map()["programmers"].Array()[1].Map()["firstName"].Str)
- }
-}
-func TestBasic3(t *testing.T) {
- var mtok Result
- if Parse(basicJSON).Get("loggy.programmers").Get("1").
- Get("firstName").Str != "Jason" {
- t.Fatalf("expected %v, got %v", "Jason", Parse(basicJSON).
- Get("loggy.programmers").Get("1").Get("firstName").Str)
- }
- var token Result
- if token = Parse("-102"); token.Num != -102 {
- t.Fatalf("expected %v, got %v", -102, token.Num)
- }
- if token = Parse("102"); token.Num != 102 {
- t.Fatalf("expected %v, got %v", 102, token.Num)
- }
- if token = Parse("102.2"); token.Num != 102.2 {
- t.Fatalf("expected %v, got %v", 102.2, token.Num)
- }
- if token = Parse(`"hello"`); token.Str != "hello" {
- t.Fatalf("expected %v, got %v", "hello", token.Str)
- }
- if token = Parse(`"\"he\nllo\""`); token.Str != "\"he\nllo\"" {
- t.Fatalf("expected %v, got %v", "\"he\nllo\"", token.Str)
- }
- mtok = get(basicJSON, "loggy.programmers.#.firstName")
- if len(mtok.Array()) != 4 {
- t.Fatalf("expected 4, got %v", len(mtok.Array()))
- }
- for i, ex := range []string{"Brett", "Jason", "Elliotte", "1002.3"} {
- if mtok.Array()[i].String() != ex {
- t.Fatalf("expected '%v', got '%v'", ex, mtok.Array()[i].String())
- }
- }
- mtok = get(basicJSON, "loggy.programmers.#.asd")
- if mtok.Type != JSON {
- t.Fatalf("expected %v, got %v", JSON, mtok.Type)
- }
- if len(mtok.Array()) != 0 {
- 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)
- }
- if get(basicJSON, "items.3.points.1.#").Num != 2 {
- t.Fatalf("expected 2, got %v",
- get(basicJSON, "items.3.points.1.#").Num)
- }
- if get(basicJSON, "items.#").Num != 8 {
- t.Fatalf("expected 6, got %v", get(basicJSON, "items.#").Num)
- }
- if get(basicJSON, "vals.#").Num != 4 {
- t.Fatalf("expected 4, got %v", get(basicJSON, "vals.#").Num)
- }
- if !get(basicJSON, "name.last").Exists() {
- t.Fatal("expected true, got false")
- }
- token := get(basicJSON, "name.here")
- if token.String() != "B\\\"R" {
- t.Fatal("expecting 'B\\\"R'", "got", token.String())
- }
- token = get(basicJSON, "arr.#")
- if token.String() != "6" {
- fmt.Printf("%#v\n", token)
- t.Fatal("expecting 6", "got", token.String())
- }
- token = get(basicJSON, "arr.3.hello")
- if token.String() != "world" {
- t.Fatal("expecting 'world'", "got", token.String())
- }
- _ = token.Value().(string)
- token = get(basicJSON, "name.first")
- if token.String() != "tom" {
- t.Fatal("expecting 'tom'", "got", token.String())
- }
- _ = token.Value().(string)
- token = get(basicJSON, "name.last")
- if token.String() != "" {
- t.Fatal("expecting ''", "got", token.String())
- }
- if token.Value() != nil {
- t.Fatal("should be nil")
- }
-}
-func TestBasic5(t *testing.T) {
- token := get(basicJSON, "age")
- if token.String() != "100" {
- t.Fatal("expecting '100'", "got", token.String())
- }
- _ = token.Value().(float64)
- token = get(basicJSON, "happy")
- if token.String() != "true" {
- t.Fatal("expecting 'true'", "got", token.String())
- }
- _ = token.Value().(bool)
- token = get(basicJSON, "immortal")
- if token.String() != "false" {
- t.Fatal("expecting 'false'", "got", token.String())
- }
- _ = token.Value().(bool)
- token = get(basicJSON, "noop")
- if token.String() != `{"what is a wren?":"a bird"}` {
- t.Fatal("expecting '"+`{"what is a wren?":"a bird"}`+"'", "got",
- token.String())
- }
- _ = token.Value().(map[string]interface{})
-
- if get(basicJSON, "").Value() != nil {
- t.Fatal("should be nil")
- }
-
- get(basicJSON, "vals.hello")
-
- type msi = map[string]interface{}
- type fi = []interface{}
- mm := Parse(basicJSON).Value().(msi)
- fn := mm["loggy"].(msi)["programmers"].(fi)[1].(msi)["firstName"].(string)
- if fn != "Jason" {
- t.Fatalf("expecting %v, got %v", "Jason", fn)
- }
-}
-func TestUnicode(t *testing.T) {
- var json = `{"key":0,"的情况下解":{"key":1,"的情况":2}}`
- if Get(json, "的情况下解.key").Num != 1 {
- t.Fatal("fail")
- }
- if Get(json, "的情况下解.的情况").Num != 2 {
- t.Fatal("fail")
- }
- if Get(json, "的情况下解.的?况").Num != 2 {
- t.Fatal("fail")
- }
- if Get(json, "的情况下解.的?*").Num != 2 {
- t.Fatal("fail")
- }
- if Get(json, "的情况下解.*?况").Num != 2 {
- t.Fatal("fail")
- }
- if Get(json, "的情?下解.*?况").Num != 2 {
- t.Fatal("fail")
- }
- if Get(json, "的情下解.*?况").Num != 0 {
- t.Fatal("fail")
- }
-}
-
-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))
- assert(t, Result{Type: Null}.Less(Result{Type: True}, true))
- assert(t, Result{Type: Null}.Less(Result{Type: JSON}, true))
- assert(t, Result{Type: Null}.Less(Result{Type: Number}, true))
- 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, stringLessInsensitive("abcde", "BBCDE"))
- assert(t, stringLessInsensitive("abcde", "bBCDE"))
- assert(t, stringLessInsensitive("Abcde", "BBCDE"))
- assert(t, stringLessInsensitive("Abcde", "bBCDE"))
- assert(t, !stringLessInsensitive("bbcde", "aBCDE"))
- assert(t, !stringLessInsensitive("bbcde", "ABCDE"))
- assert(t, !stringLessInsensitive("Bbcde", "aBCDE"))
- assert(t, !stringLessInsensitive("Bbcde", "ABCDE"))
- assert(t, !stringLessInsensitive("abcde", "ABCDE"))
- assert(t, !stringLessInsensitive("Abcde", "ABCDE"))
- assert(t, !stringLessInsensitive("abcde", "ABCDE"))
- assert(t, !stringLessInsensitive("ABCDE", "ABCDE"))
- assert(t, !stringLessInsensitive("abcde", "abcde"))
- assert(t, !stringLessInsensitive("123abcde", "123Abcde"))
- assert(t, !stringLessInsensitive("123Abcde", "123Abcde"))
- assert(t, !stringLessInsensitive("123Abcde", "123abcde"))
- assert(t, !stringLessInsensitive("123abcde", "123abcde"))
- assert(t, !stringLessInsensitive("124abcde", "123abcde"))
- assert(t, !stringLessInsensitive("124Abcde", "123Abcde"))
- assert(t, !stringLessInsensitive("124Abcde", "123abcde"))
- assert(t, !stringLessInsensitive("124abcde", "123abcde"))
- assert(t, stringLessInsensitive("124abcde", "125abcde"))
- assert(t, stringLessInsensitive("124Abcde", "125Abcde"))
- assert(t, stringLessInsensitive("124Abcde", "125abcde"))
- assert(t, stringLessInsensitive("124abcde", "125abcde"))
-}
-
-func TestIssue6(t *testing.T) {
- data := `{
- "code": 0,
- "msg": "",
- "data": {
- "sz002024": {
- "qfqday": [
- [
- "2014-01-02",
- "8.93",
- "9.03",
- "9.17",
- "8.88",
- "621143.00"
- ],
- [
- "2014-01-03",
- "9.03",
- "9.30",
- "9.47",
- "8.98",
- "1624438.00"
- ]
- ]
- }
- }
- }`
-
- var num []string
- for _, v := range Get(data, "data.sz002024.qfqday.0").Array() {
- num = append(num, v.String())
- }
- if fmt.Sprintf("%v", num) != "[2014-01-02 8.93 9.03 9.17 8.88 621143.00]" {
- t.Fatalf("invalid result")
- }
-}
-
-var exampleJSON = `{
- "widget": {
- "debug": "on",
- "window": {
- "title": "Sample Konfabulator Widget",
- "name": "main_window",
- "width": 500,
- "height": 500
- },
- "image": {
- "src": "Images/Sun.png",
- "hOffset": 250,
- "vOffset": 250,
- "alignment": "center"
- },
- "text": {
- "data": "Click Here",
- "size": 36,
- "style": "bold",
- "vOffset": 100,
- "alignment": "center",
- "onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;"
- }
- }
-}`
-
-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{}
- if err := json.Unmarshal([]byte(exampleJSON), &m2); err != nil {
- t.Fatal(err)
- }
- b1, err := json.Marshal(m1)
- if err != nil {
- t.Fatal(err)
- }
- b2, err := json.Marshal(m2)
- if err != nil {
- t.Fatal(err)
- }
- if bytes.Compare(b1, b2) != 0 {
- t.Fatal("b1 != b2")
- }
-}
-
-func TestSingleArrayValue(t *testing.T) {
- var json = `{"key": "value","key2":[1,2,3,4,"A"]}`
- var result = Get(json, "key")
- var array = result.Array()
- if len(array) != 1 {
- t.Fatal("array is empty")
- }
- if array[0].String() != "value" {
- t.Fatalf("got %s, should be %s", array[0].String(), "value")
- }
-
- array = Get(json, "key2.#").Array()
- if len(array) != 1 {
- t.Fatalf("got '%v', expected '%v'", len(array), 1)
- }
-
- array = Get(json, "key3").Array()
- if len(array) != 0 {
- t.Fatalf("got '%v', expected '%v'", len(array), 0)
- }
-
-}
-
-var manyJSON = ` {
- "a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{
- "a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{
- "a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{
- "a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{
- "a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{
- "a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{
- "a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"hello":"world"
- }}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
- "position":{"type":"Point","coordinates":[-115.24,33.09]},
- "loves":["world peace"],
- "name":{"last":"Anderson","first":"Nancy"},
- "age":31
- "":{"a":"emptya","b":"emptyb"},
- "name.last":"Yellow",
- "name.first":"Cat",
-}`
-
-func combine(results []Result) string {
- return fmt.Sprintf("%v", results)
-}
-func TestManyBasic(t *testing.T) {
- testWatchForFallback = true
- defer func() {
- testWatchForFallback = false
- }()
- testMany := func(shouldFallback bool, expect string, paths ...string) {
- results := GetManyBytes(
- []byte(manyJSON),
- paths...,
- )
- if len(results) != len(paths) {
- t.Fatalf("expected %v, got %v", len(paths), len(results))
- }
- if fmt.Sprintf("%v", results) != expect {
- 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")
- testMany(false, `[["world peace"]]`, "loves")
- testMany(false, `[{"last":"Anderson","first":"Nancy"} Nancy]`, "name",
- "name.first")
- testMany(true, `[]`, strings.Repeat("a.", 40)+"hello")
- res := Get(manyJSON, strings.Repeat("a.", 48)+"a")
- testMany(true, `[`+res.String()+`]`, strings.Repeat("a.", 48)+"a")
- // these should fallback
- 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) {
- var result []Result
- for i := 0; i < 2; i++ {
- var which string
- if i == 0 {
- which = "Get"
- result = nil
- for j := 0; j < len(expected); j++ {
- if bytes {
- result = append(result, GetBytes([]byte(json), paths[j]))
- } else {
- result = append(result, Get(json, paths[j]))
- }
- }
- } else if i == 1 {
- which = "GetMany"
- if bytes {
- result = GetManyBytes([]byte(json), paths...)
- } else {
- result = GetMany(json, paths...)
- }
- }
- for j := 0; j < len(expected); j++ {
- if result[j].String() != expected[j] {
- t.Fatalf("Using key '%s' for '%s'\nexpected '%v', got '%v'",
- paths[j], which, expected[j], result[j].String())
- }
- }
- }
-}
-func TestIssue20(t *testing.T) {
- json := `{ "name": "FirstName", "name1": "FirstName1", ` +
- `"address": "address1", "addressDetails": "address2", }`
- paths := []string{"name", "name1", "address", "addressDetails"}
- expected := []string{"FirstName", "FirstName1", "address1", "address2"}
- t.Run("SingleMany", func(t *testing.T) {
- testMany(t, json, paths,
- expected)
- })
-}
-
-func TestIssue21(t *testing.T) {
- json := `{ "Level1Field1":3,
- "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"}
- t.Run("SingleMany", func(t *testing.T) {
- testMany(t, json, paths,
- expected)
- })
-}
-
-func TestRandomMany(t *testing.T) {
- var lstr string
- defer func() {
- if v := recover(); v != nil {
- println("'" + hex.EncodeToString([]byte(lstr)) + "'")
- println("'" + lstr + "'")
- panic(v)
- }
- }()
- rand.Seed(time.Now().UnixNano())
- b := make([]byte, 512)
- for i := 0; i < 50000; i++ {
- n, err := rand.Read(b[:rand.Int()%len(b)])
- if err != nil {
- t.Fatal(err)
- }
- lstr = string(b[:n])
- paths := make([]string, rand.Int()%64)
- for i := range paths {
- var b []byte
- n := rand.Int() % 5
- for j := 0; j < n; j++ {
- if j > 0 {
- b = append(b, '.')
- }
- nn := rand.Int() % 10
- for k := 0; k < nn; k++ {
- b = append(b, 'a'+byte(rand.Int()%26))
- }
- }
- paths[i] = string(b)
- }
- GetMany(lstr, paths...)
- }
-}
-
-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",
- "Tagged": "KO",
- "NotTagged": true,
- "unsettable": 101,
- "Nested": {
- "Yellow": "Green",
- "yellow": "yellow"
- },
- "nestedTagged": {
- "Green": "Green",
- "Map": {
- "this": "that",
- "and": "the other thing"
- },
- "Ints": {
- "Uint": 99,
- "Uint16": 16,
- "Uint32": 32,
- "Uint64": 65
- },
- "Uints": {
- "int": -99,
- "Int": -98,
- "Int16": -16,
- "Int32": -32,
- "int64": -64,
- "Int64": -65
- },
- "Uints": {
- "Float32": 32.32,
- "Float64": 64.64
- },
- "Byte": 254,
- "Bool": true
- },
- "LeftOut": "you shouldn't be here",
- "SelfPtr": {"tagged":"OK","nestedTagged":{"Ints":{"Uint32":32}}},
- "SelfSlice": [{"tagged":"OK","nestedTagged":{"Ints":{"Uint32":32}}}],
- "SelfSlicePtr": [{"tagged":"OK","nestedTagged":{"Ints":{"Uint32":32}}}],
- "SelfPtrSlice": [{"tagged":"OK","nestedTagged":{"Ints":{"Uint32":32}}}],
- "interface": "Tile38 Rocks!",
- "Interface": "Please Download",
- "Array": [0,2,3,4,5],
- "time": "2017-05-07T13:24:43-07:00",
- "Binary": "R0lGODlhPQBEAPeo",
- "NonBinary": [9,3,100,115]
-}
-`
-
-func testvalid(t *testing.T, json string, expect bool) {
- t.Helper()
- _, ok := validpayload([]byte(json), 0)
- if ok != expect {
- t.Fatal("mismatch")
- }
-}
-
-func TestValidBasic(t *testing.T) {
- testvalid(t, "0", true)
- testvalid(t, "00", false)
- testvalid(t, "-00", false)
- testvalid(t, "-.", false)
- testvalid(t, "0.0", true)
- testvalid(t, "10.0", true)
- testvalid(t, "10e1", true)
- testvalid(t, "10EE", false)
- testvalid(t, "10E-", false)
- testvalid(t, "10E+", false)
- testvalid(t, "10E123", true)
- testvalid(t, "10E-123", true)
- testvalid(t, "10E-0123", true)
- testvalid(t, "", false)
- testvalid(t, " ", false)
- testvalid(t, "{}", true)
- testvalid(t, "{", false)
- testvalid(t, "-", false)
- testvalid(t, "-1", true)
- testvalid(t, "-1.", false)
- testvalid(t, "-1.0", true)
- testvalid(t, " -1.0", true)
- testvalid(t, " -1.0 ", true)
- testvalid(t, "-1.0 ", true)
- testvalid(t, "-1.0 i", false)
- testvalid(t, "-1.0 i", false)
- testvalid(t, "true", true)
- testvalid(t, " true", true)
- testvalid(t, " true ", true)
- testvalid(t, " True ", false)
- testvalid(t, " tru", false)
- testvalid(t, "false", true)
- testvalid(t, " false", true)
- testvalid(t, " false ", true)
- testvalid(t, " False ", false)
- testvalid(t, " fals", false)
- testvalid(t, "null", true)
- testvalid(t, " null", true)
- testvalid(t, " null ", true)
- testvalid(t, " Null ", false)
- testvalid(t, " nul", false)
- testvalid(t, " []", true)
- testvalid(t, " [true]", true)
- testvalid(t, " [ true, null ]", true)
- testvalid(t, " [ true,]", false)
- testvalid(t, `{"hello":"world"}`, true)
- testvalid(t, `{ "hello": "world" }`, true)
- testvalid(t, `{ "hello": "world", }`, false)
- testvalid(t, `{"a":"b",}`, false)
- testvalid(t, `{"a":"b","a"}`, false)
- testvalid(t, `{"a":"b","a":}`, false)
- testvalid(t, `{"a":"b","a":1}`, true)
- testvalid(t, `{"a":"b",2"1":2}`, false)
- testvalid(t, `{"a":"b","a": 1, "c":{"hi":"there"} }`, true)
- testvalid(t, `{"a":"b","a": 1, "c":{"hi":"there", "easy":["going",`+
- `{"mixed":"bag"}]} }`, true)
- testvalid(t, `""`, true)
- testvalid(t, `"`, false)
- testvalid(t, `"\n"`, true)
- testvalid(t, `"\"`, false)
- testvalid(t, `"\\"`, true)
- testvalid(t, `"a\\b"`, true)
- testvalid(t, `"a\\b\\\"a"`, true)
- testvalid(t, `"a\\b\\\uFFAAa"`, true)
- testvalid(t, `"a\\b\\\uFFAZa"`, false)
- testvalid(t, `"a\\b\\\uFFA"`, false)
- testvalid(t, string(complicatedJSON), true)
- testvalid(t, string(exampleJSON), true)
-}
-
-var jsonchars = []string{"{", "[", ",", ":", "}", "]", "1", "0", "true",
- "false", "null", `""`, `"\""`, `"a"`}
-
-func makeRandomJSONChars(b []byte) {
- var bb []byte
- for len(bb) < len(b) {
- bb = append(bb, jsonchars[rand.Int()%len(jsonchars)]...)
- }
- copy(b, bb[:len(b)])
-}
-
-func TestValidRandom(t *testing.T) {
- rand.Seed(time.Now().UnixNano())
- b := make([]byte, 100000)
- start := time.Now()
- for time.Since(start) < time.Second*3 {
- n := rand.Int() % len(b)
- rand.Read(b[:n])
- validpayload(b[:n], 0)
- }
-
- start = time.Now()
- for time.Since(start) < time.Second*3 {
- n := rand.Int() % len(b)
- makeRandomJSONChars(b[:n])
- validpayload(b[:n], 0)
- }
-}
-
-func TestGetMany47(t *testing.T) {
- json := `{"bar": {"id": 99, "mybar": "my mybar" }, "foo": ` +
- `{"myfoo": [605]}}`
- paths := []string{"foo.myfoo", "bar.id", "bar.mybar", "bar.mybarx"}
- expected := []string{"[605]", "99", "my mybar", ""}
- results := GetMany(json, paths...)
- if len(expected) != len(results) {
- t.Fatalf("expected %v, got %v", len(expected), len(results))
- }
- for i, path := range paths {
- if results[i].String() != expected[i] {
- t.Fatalf("expected '%v', got '%v' for path '%v'", expected[i],
- results[i].String(), path)
- }
- }
-}
-
-func TestGetMany48(t *testing.T) {
- json := `{"bar": {"id": 99, "xyz": "my xyz"}, "foo": {"myfoo": [605]}}`
- paths := []string{"foo.myfoo", "bar.id", "bar.xyz", "bar.abc"}
- expected := []string{"[605]", "99", "my xyz", ""}
- results := GetMany(json, paths...)
- if len(expected) != len(results) {
- t.Fatalf("expected %v, got %v", len(expected), len(results))
- }
- for i, path := range paths {
- if results[i].String() != expected[i] {
- t.Fatalf("expected '%v', got '%v' for path '%v'", expected[i],
- results[i].String(), path)
- }
- }
-}
-
-func TestResultRawForLiteral(t *testing.T) {
- for _, lit := range []string{"null", "true", "false"} {
- result := Parse(lit)
- if result.Raw != lit {
- t.Fatalf("expected '%v', got '%v'", lit, result.Raw)
- }
- }
-}
-
-func TestNullArray(t *testing.T) {
- n := len(Get(`{"data":null}`, "data").Array())
- if n != 0 {
- t.Fatalf("expected '%v', got '%v'", 0, n)
- }
- n = len(Get(`{}`, "data").Array())
- if n != 0 {
- t.Fatalf("expected '%v', got '%v'", 0, n)
- }
- n = len(Get(`{"data":[]}`, "data").Array())
- if n != 0 {
- t.Fatalf("expected '%v', got '%v'", 0, n)
- }
- n = len(Get(`{"data":[null]}`, "data").Array())
- if n != 1 {
- t.Fatalf("expected '%v', got '%v'", 1, n)
- }
-}
-
-// 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}`
- r = GetMany(json, "Nounce", "Buys", "Sells", "Fills")
- if strings.Replace(fmt.Sprintf("%v", r), " ", "", -1) != "[6115]" {
- t.Fatalf("expected '%v', got '%v'", "[6115]",
- strings.Replace(fmt.Sprintf("%v", r), " ", "", -1))
- }
- r = GetMany(json, "Nounce", "Buys", "Sells")
- if strings.Replace(fmt.Sprintf("%v", r), " ", "", -1) != "[6115]" {
- t.Fatalf("expected '%v', got '%v'", "[6115]",
- strings.Replace(fmt.Sprintf("%v", r), " ", "", -1))
- }
- r = GetMany(json, "Nounce")
- if strings.Replace(fmt.Sprintf("%v", r), " ", "", -1) != "[6115]" {
- t.Fatalf("expected '%v', got '%v'", "[6115]",
- strings.Replace(fmt.Sprintf("%v", r), " ", "", -1))
- }
-}
-
-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")
- expected := []string{"4", "5", "2", ""}
- for i, r := range results {
- if r.String() != expected[i] {
- t.Fatalf("expected %v, got %v", expected[i], r.String())
- }
- }
-}
-func TestIssue58(t *testing.T) {
- json := `{"data":[{"uid": 1},{"uid": 2}]}`
- res := Get(json, `data.#[uid!=1]`).Raw
- if res != `{"uid": 2}` {
- t.Fatalf("expected '%v', got '%v'", `{"uid": 1}`, res)
- }
-}
-
-func TestObjectGrouping(t *testing.T) {
- json := `
-[
- true,
- {"name":"tom"},
- false,
- {"name":"janet"},
- null
-]
-`
- res := Get(json, "#.name")
- if res.String() != `["tom","janet"]` {
- t.Fatalf("expected '%v', got '%v'", `["tom","janet"]`, res.String())
- }
-}
-
-func TestJSONLines(t *testing.T) {
- json := `
-true
-false
-{"name":"tom"}
-[1,2,3,4,5]
-{"name":"janet"}
-null
-12930.1203
- `
- paths := []string{"..#", "..0", "..2.name", "..#.name", "..6", "..7"}
- ress := []string{"7", "true", "tom", `["tom","janet"]`, "12930.1203", ""}
- for i, path := range paths {
- res := Get(json, path)
- if res.String() != ress[i] {
- t.Fatalf("expected '%v', got '%v'", ress[i], res.String())
- }
- }
-
- json = `
-{"name": "Gilbert", "wins": [["straight", "7♣"], ["one pair", "10♥"]]}
-{"name": "Alexa", "wins": [["two pair", "4♠"], ["two pair", "9♠"]]}
-{"name": "May", "wins": []}
-{"name": "Deloise", "wins": [["three of a kind", "5♣"]]}
-`
-
- var i int
- lines := strings.Split(strings.TrimSpace(json), "\n")
- ForEachLine(json, func(line Result) bool {
- if line.Raw != lines[i] {
- t.Fatalf("expected '%v', got '%v'", lines[i], line.Raw)
- }
- i++
- return true
- })
- if i != 4 {
- t.Fatalf("expected '%v', got '%v'", 4, i)
- }
-
-}
-
-func TestNumUint64String(t *testing.T) {
- var i int64 = 9007199254740993 //2^53 + 1
- j := fmt.Sprintf(`{"data": [ %d, "hello" ] }`, i)
- res := Get(j, "data.0")
- if res.String() != "9007199254740993" {
- t.Fatalf("expected '%v', got '%v'", "9007199254740993", res.String())
- }
-}
-
-func TestNumInt64String(t *testing.T) {
- var i int64 = -9007199254740993
- j := fmt.Sprintf(`{"data":[ "hello", %d ]}`, i)
- res := Get(j, "data.1")
- if res.String() != "-9007199254740993" {
- t.Fatalf("expected '%v', got '%v'", "-9007199254740993", res.String())
- }
-}
-
-func TestNumBigString(t *testing.T) {
- i := "900719925474099301239109123101" // very big
- j := fmt.Sprintf(`{"data":[ "hello", "%s" ]}`, i)
- res := Get(j, "data.1")
- if res.String() != "900719925474099301239109123101" {
- t.Fatalf("expected '%v', got '%v'", "900719925474099301239109123101",
- res.String())
- }
-}
-
-func TestNumFloatString(t *testing.T) {
- var i int64 = -9007199254740993
- 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())
- }
-}
-
-func TestDuplicateKeys(t *testing.T) {
- // this is vaild json according to the JSON spec
- var json = `{"name": "Alex","name": "Peter"}`
- if Parse(json).Get("name").String() !=
- Parse(json).Map()["name"].String() {
- t.Fatalf("expected '%v', got '%v'",
- Parse(json).Get("name").String(),
- Parse(json).Map()["name"].String(),
- )
- }
- if !Valid(json) {
- t.Fatal("should be valid")
- }
-}
-
-func TestArrayValues(t *testing.T) {
- var json = `{"array": ["PERSON1","PERSON2",0],}`
- values := Get(json, "array").Array()
- var output string
- for i, val := range values {
- if i > 0 {
- output += "\n"
- }
- output += fmt.Sprintf("%#v", val)
- }
- expect := strings.Join([]string{
- `gjson.Result{Type:3, Raw:"\"PERSON1\"", Str:"PERSON1", Num:0, ` +
- `Index:0}`,
- `gjson.Result{Type:3, Raw:"\"PERSON2\"", Str:"PERSON2", Num:0, ` +
- `Index:0}`,
- `gjson.Result{Type:2, Raw:"0", Str:"", Num:0, Index:0}`,
- }, "\n")
- if output != expect {
- t.Fatalf("expected '%v', got '%v'", expect, output)
- }
-
-}
-
-func BenchmarkValid(b *testing.B) {
- for i := 0; i < b.N; i++ {
- Valid(complicatedJSON)
- }
-}
-
-func BenchmarkValidBytes(b *testing.B) {
- complicatedJSON := []byte(complicatedJSON)
- for i := 0; i < b.N; i++ {
- ValidBytes(complicatedJSON)
- }
-}
-
-func BenchmarkGoStdlibValidBytes(b *testing.B) {
- complicatedJSON := []byte(complicatedJSON)
- for i := 0; i < b.N; i++ {
- json.Valid(complicatedJSON)
- }
-}
-
-func TestModifier(t *testing.T) {
- json := `{"other":{"hello":"world"},"arr":[1,2,3,4,5,6]}`
- opts := *pretty.DefaultOptions
- opts.SortKeys = true
- exp := string(pretty.PrettyOptions([]byte(json), &opts))
- res := Get(json, `@pretty:{"sortKeys":true}`).String()
- if res != exp {
- t.Fatalf("expected '%v', got '%v'", exp, res)
- }
- res = Get(res, "@pretty|@reverse|@ugly").String()
- if res != json {
- t.Fatalf("expected '%v', got '%v'", json, res)
- }
- if res := Get(res, "@this").String(); res != json {
- t.Fatalf("expected '%v', got '%v'", json, res)
- }
- if res := Get(res, "other.@this").String(); res != `{"hello":"world"}` {
- t.Fatalf("expected '%v', got '%v'", json, res)
- }
- res = Get(res, "@pretty|@reverse|arr|@reverse|2").String()
- if res != "4" {
- t.Fatalf("expected '%v', got '%v'", "4", res)
- }
- AddModifier("case", func(json, arg string) string {
- if arg == "upper" {
- return strings.ToUpper(json)
- }
- if arg == "lower" {
- return strings.ToLower(json)
- }
- return json
- })
- res = Get(json, "other|@case:upper").String()
- if res != `{"HELLO":"WORLD"}` {
- t.Fatalf("expected '%v', got '%v'", `{"HELLO":"WORLD"}`, res)
- }
-}
-
-func TestChaining(t *testing.T) {
- json := `{
- "info": {
- "friends": [
- {"first": "Dale", "last": "Murphy", "age": 44},
- {"first": "Roger", "last": "Craig", "age": 68},
- {"first": "Jane", "last": "Murphy", "age": 47}
- ]
- }
- }`
- res := Get(json, "info.friends|0|first").String()
- if res != "Dale" {
- t.Fatalf("expected '%v', got '%v'", "Dale", res)
- }
- res = Get(json, "info.friends|@reverse|0|age").String()
- if res != "47" {
- t.Fatalf("expected '%v', got '%v'", "47", res)
- }
- res = Get(json, "@ugly|i\\nfo|friends.0.first").String()
- if res != "Dale" {
- t.Fatalf("expected '%v', got '%v'", "Dale", res)
- }
-}
-
-func TestSplitPipe(t *testing.T) {
- split := func(t *testing.T, path, el, er string, eo bool) {
- t.Helper()
- left, right, ok := splitPossiblePipe(path)
- // fmt.Printf("%-40s [%v] [%v] [%v]\n", path, left, right, ok)
- if left != el || right != er || ok != eo {
- t.Fatalf("expected '%v/%v/%v', got '%v/%v/%v",
- el, er, eo, left, right, ok)
- }
- }
-
- split(t, "hello", "", "", false)
- split(t, "hello.world", "", "", false)
- split(t, "hello|world", "hello", "world", true)
- split(t, "hello\\|world", "", "", false)
- split(t, "hello.#", "", "", false)
- split(t, `hello.#[a|1="asdf\"|1324"]#\|that`, "", "", false)
- 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) {
- json := `
- [
- {
- "c":[
- {"a":10.11}
- ]
- }, {
- "c":[
- {"a":11.11}
- ]
- }
- ]`
- res := Get(json, "@ugly|#.c.#[a=10.11]").String()
- if res != `[{"a":10.11}]` {
- t.Fatalf("expected '%v', got '%v'", `[{"a":10.11}]`, res)
- }
- res = Get(json, "@ugly|#.c.#").String()
- if res != `[1,1]` {
- t.Fatalf("expected '%v', got '%v'", `[1,1]`, res)
- }
- res = Get(json, "@reverse|0|c|0|a").String()
- if res != "11.11" {
- t.Fatalf("expected '%v', got '%v'", "11.11", res)
- }
- res = Get(json, "#.c|#").String()
- if res != "2" {
- t.Fatalf("expected '%v', got '%v'", "2", res)
- }
-}
-
-func TestPipeDotMixing(t *testing.T) {
- json := `{
- "info": {
- "friends": [
- {"first": "Dale", "last": "Murphy", "age": 44},
- {"first": "Roger", "last": "Craig", "age": 68},
- {"first": "Jane", "last": "Murphy", "age": 47}
- ]
- }
- }`
- var res string
- res = Get(json, `info.friends.#[first="Dale"].last`).String()
- if res != "Murphy" {
- t.Fatalf("expected '%v', got '%v'", "Murphy", res)
- }
- res = Get(json, `info|friends.#[first="Dale"].last`).String()
- if res != "Murphy" {
- t.Fatalf("expected '%v', got '%v'", "Murphy", res)
- }
- res = Get(json, `info|friends.#[first="Dale"]|last`).String()
- if res != "Murphy" {
- t.Fatalf("expected '%v', got '%v'", "Murphy", res)
- }
- res = Get(json, `info|friends|#[first="Dale"]|last`).String()
- if res != "Murphy" {
- t.Fatalf("expected '%v', got '%v'", "Murphy", res)
- }
- res = Get(json, `@ugly|info|friends|#[first="Dale"]|last`).String()
- if res != "Murphy" {
- t.Fatalf("expected '%v', got '%v'", "Murphy", res)
- }
- res = Get(json, `@ugly|info.@ugly|friends|#[first="Dale"]|last`).String()
- if res != "Murphy" {
- t.Fatalf("expected '%v', got '%v'", "Murphy", res)
- }
- res = Get(json, `@ugly.info|@ugly.friends|#[first="Dale"]|last`).String()
- if res != "Murphy" {
- t.Fatalf("expected '%v', got '%v'", "Murphy", res)
- }
-}
-
-func TestDeepSelectors(t *testing.T) {
- json := `{
- "info": {
- "friends": [
- {
- "first": "Dale", "last": "Murphy",
- "extra": [10,20,30],
- "details": {
- "city": "Tempe",
- "state": "Arizona"
- }
- },
- {
- "first": "Roger", "last": "Craig",
- "extra": [40,50,60],
- "details": {
- "city": "Phoenix",
- "state": "Arizona"
- }
- }
- ]
- }
- }`
- var res string
- res = Get(json, `info.friends.#[first="Dale"].extra.0`).String()
- if res != "10" {
- t.Fatalf("expected '%v', got '%v'", "10", res)
- }
- res = Get(json, `info.friends.#[first="Dale"].extra|0`).String()
- if res != "10" {
- t.Fatalf("expected '%v', got '%v'", "10", res)
- }
- res = Get(json, `info.friends.#[first="Dale"]|extra|0`).String()
- if res != "10" {
- t.Fatalf("expected '%v', got '%v'", "10", res)
- }
- res = Get(json, `info.friends.#[details.city="Tempe"].last`).String()
- if res != "Murphy" {
- t.Fatalf("expected '%v', got '%v'", "Murphy", res)
- }
- res = Get(json, `info.friends.#[details.city="Phoenix"].last`).String()
- if res != "Craig" {
- t.Fatalf("expected '%v', got '%v'", "Craig", res)
- }
- res = Get(json, `info.friends.#[details.state="Arizona"].last`).String()
- if res != "Murphy" {
- t.Fatalf("expected '%v', got '%v'", "Murphy", res)
- }
-}
-
-func TestMultiArrayEx(t *testing.T) {
- json := `{
- "info": {
- "friends": [
- {
- "first": "Dale", "last": "Murphy", "kind": "Person",
- "cust1": true,
- "extra": [10,20,30],
- "details": {
- "city": "Tempe",
- "state": "Arizona"
- }
- },
- {
- "first": "Roger", "last": "Craig", "kind": "Person",
- "cust2": false,
- "extra": [40,50,60],
- "details": {
- "city": "Phoenix",
- "state": "Arizona"
- }
- }
- ]
- }
- }`
-
- var res string
-
- res = Get(json, `info.friends.#[kind="Person"]#.kind|0`).String()
- if res != "Person" {
- t.Fatalf("expected '%v', got '%v'", "Person", res)
- }
- res = Get(json, `info.friends.#.kind|0`).String()
- if res != "Person" {
- t.Fatalf("expected '%v', got '%v'", "Person", res)
- }
-
- res = Get(json, `info.friends.#[kind="Person"]#.kind`).String()
- if res != `["Person","Person"]` {
- t.Fatalf("expected '%v', got '%v'", `["Person","Person"]`, res)
- }
- res = Get(json, `info.friends.#.kind`).String()
- if res != `["Person","Person"]` {
- t.Fatalf("expected '%v', got '%v'", `["Person","Person"]`, res)
- }
-
- res = Get(json, `info.friends.#[kind="Person"]#|kind`).String()
- if res != `` {
- t.Fatalf("expected '%v', got '%v'", ``, res)
- }
- res = Get(json, `info.friends.#|kind`).String()
- if res != `` {
- t.Fatalf("expected '%v', got '%v'", ``, res)
- }
-
- res = Get(json, `i*.f*.#[kind="Other"]#`).String()
- if res != `[]` {
- t.Fatalf("expected '%v', got '%v'", `[]`, res)
- }
-}
-
-func TestQueries(t *testing.T) {
- json := `{
- "info": {
- "friends": [
- {
- "first": "Dale", "last": "Murphy", "kind": "Person",
- "cust1": true,
- "extra": [10,20,30],
- "details": {
- "city": "Tempe",
- "state": "Arizona"
- }
- },
- {
- "first": "Roger", "last": "Craig", "kind": "Person",
- "cust2": false,
- "extra": [40,50,60],
- "details": {
- "city": "Phoenix",
- "state": "Arizona"
- }
- }
- ]
- }
- }`
-
- // numbers
- assert(t, Get(json, "i*.f*.#[extra.0<11].first").Exists())
- assert(t, Get(json, "i*.f*.#[extra.0<=11].first").Exists())
- assert(t, !Get(json, "i*.f*.#[extra.0<10].first").Exists())
- assert(t, Get(json, "i*.f*.#[extra.0<=10].first").Exists())
- assert(t, Get(json, "i*.f*.#[extra.0=10].first").Exists())
- assert(t, !Get(json, "i*.f*.#[extra.0=11].first").Exists())
- assert(t, Get(json, "i*.f*.#[extra.0!=10].first").String() == "Roger")
- assert(t, Get(json, "i*.f*.#[extra.0>10].first").String() == "Roger")
- assert(t, Get(json, "i*.f*.#[extra.0>=10].first").String() == "Dale")
-
- // strings
- assert(t, Get(json, `i*.f*.#[extra.0<"11"].first`).Exists())
- assert(t, Get(json, `i*.f*.#[first>"Dale"].last`).String() == "Craig")
- assert(t, Get(json, `i*.f*.#[first>="Dale"].last`).String() == "Murphy")
- assert(t, Get(json, `i*.f*.#[first="Dale"].last`).String() == "Murphy")
- assert(t, Get(json, `i*.f*.#[first!="Dale"].last`).String() == "Craig")
- assert(t, !Get(json, `i*.f*.#[first<"Dale"].last`).Exists())
- assert(t, Get(json, `i*.f*.#[first<="Dale"].last`).Exists())
- assert(t, Get(json, `i*.f*.#[first%"Da*"].last`).Exists())
- assert(t, Get(json, `i*.f*.#[first%"Dale"].last`).Exists())
- assert(t, Get(json, `i*.f*.#[first%"*a*"]#|#`).String() == "1")
- assert(t, Get(json, `i*.f*.#[first%"*e*"]#|#`).String() == "2")
- assert(t, Get(json, `i*.f*.#[first!%"*e*"]#|#`).String() == "0")
-
- // trues
- assert(t, Get(json, `i*.f*.#[cust1=true].first`).String() == "Dale")
- assert(t, Get(json, `i*.f*.#[cust2=false].first`).String() == "Roger")
- assert(t, Get(json, `i*.f*.#[cust1!=false].first`).String() == "Dale")
- assert(t, Get(json, `i*.f*.#[cust2!=true].first`).String() == "Roger")
- assert(t, !Get(json, `i*.f*.#[cust1>true].first`).Exists())
- assert(t, Get(json, `i*.f*.#[cust1>=true].first`).Exists())
- assert(t, !Get(json, `i*.f*.#[cust29)#|#").Int() == 4)
- assert(t, Get(json, "friends.#(a>10)#|#").Int() == 3)
- assert(t, Get(json, "friends.#(a>40)#|#").Int() == 0)
-}
-
-func TestSubSelectors(t *testing.T) {
- json := `{
- "info": {
- "friends": [
- {
- "first": "Dale", "last": "Murphy", "kind": "Person",
- "cust1": true,
- "extra": [10,20,30],
- "details": {
- "city": "Tempe",
- "state": "Arizona"
- }
- },
- {
- "first": "Roger", "last": "Craig", "kind": "Person",
- "cust2": false,
- "extra": [40,50,60],
- "details": {
- "city": "Phoenix",
- "state": "Arizona"
- }
- }
- ]
- }
- }`
- assert(t, Get(json, "[]").String() == "[]")
- assert(t, Get(json, "{}").String() == "{}")
- res := Get(json, `{`+
- `abc:info.friends.0.first,`+
- `info.friends.1.last,`+
- `"a`+"\r"+`a":info.friends.0.kind,`+
- `"abc":info.friends.1.kind,`+
- `{123:info.friends.1.cust2},`+
- `[info.friends.#[details.city="Phoenix"]#|#]`+
- `}.@pretty.@ugly`).String()
- // println(res)
- // {"abc":"Dale","last":"Craig","\"a\ra\"":"Person","_":{"123":false},"_":[1]}
- assert(t, Get(res, "abc").String() == "Dale")
- assert(t, Get(res, "last").String() == "Craig")
- assert(t, Get(res, "\"a\ra\"").String() == "Person")
- assert(t, Get(res, "@reverse.abc").String() == "Person")
- assert(t, Get(res, "_.123").String() == "false")
- assert(t, Get(res, "@reverse._.0").String() == "1")
- assert(t, Get(json, "info.friends.[0.first,1.extra.0]").String() ==
- `["Dale",40]`)
- assert(t, Get(json, "info.friends.#.[first,extra.0]").String() ==
- `[["Dale",10],["Roger",40]]`)
-}
-
-func TestArrayCountRawOutput(t *testing.T) {
- assert(t, Get(`[1,2,3,4]`, "#").Raw == "4")
-}
-
-func TestParseQuery(t *testing.T) {
- var path, op, value, remain string
- var ok bool
-
- path, op, value, remain, _, ok =
- parseQuery(`#(service_roles.#(=="one").()==asdf).cap`)
- assert(t, ok &&
- path == `service_roles.#(=="one").()` &&
- op == "=" &&
- value == `asdf` &&
- remain == `.cap`)
-
- 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`)
- assert(t, ok &&
- path == `first_name` &&
- op == `!%` &&
- value == `"Murphy"` &&
- remain == `.last`)
-
- path, op, value, remain, _, ok = parseQuery(`#(service_roles.#(=="one"))`)
- assert(t, ok &&
- path == `service_roles.#(=="one")` &&
- op == `` &&
- value == `` &&
- remain == ``)
-
- path, op, value, remain, _, ok =
- parseQuery(`#(a\("\"(".#(=="o\"(ne")%"ab\")").remain`)
- assert(t, ok &&
- path == `a\("\"(".#(=="o\"(ne")` &&
- op == "%" &&
- value == `"ab\")"` &&
- remain == `.remain`)
-}
-
-func TestParentSubQuery(t *testing.T) {
- var json = `{
- "topology": {
- "instances": [
- {
- "service_version": "1.2.3",
- "service_locale": {"lang": "en"},
- "service_roles": ["one", "two"]
- },
- {
- "service_version": "1.2.4",
- "service_locale": {"lang": "th"},
- "service_roles": ["three", "four"]
- },
- {
- "service_version": "1.2.2",
- "service_locale": {"lang": "en"},
- "service_roles": ["one"]
- }
- ]
- }
- }`
- res := Get(json, `topology.instances.#( service_roles.#(=="one"))#.service_version`)
- // should return two instances
- assert(t, res.String() == `["1.2.3","1.2.2"]`)
-}
-
-func TestSingleModifier(t *testing.T) {
- var data = `{"@key": "value"}`
- assert(t, Get(data, "@key").String() == "value")
- assert(t, Get(data, "\\@key").String() == "value")
-}
-
-func TestModifiersInMultipaths(t *testing.T) {
- AddModifier("case", func(json, arg string) string {
- if arg == "upper" {
- return strings.ToUpper(json)
- }
- if arg == "lower" {
- return strings.ToLower(json)
- }
- return json
- })
- json := `{"friends": [
- {"age": 44, "first": "Dale", "last": "Murphy"},
- {"age": 68, "first": "Roger", "last": "Craig"},
- {"age": 47, "first": "Jane", "last": "Murphy"}
- ]}`
-
- res := Get(json, `friends.#.{age,first|@case:upper}|@ugly`)
- exp := `[{"age":44,"@case:upper":"DALE"},{"age":68,"@case:upper":"ROGER"},{"age":47,"@case:upper":"JANE"}]`
- assert(t, res.Raw == exp)
-
- res = Get(json, `{friends.#.{age,first:first|@case:upper}|0.first}`)
- exp = `{"first":"DALE"}`
- assert(t, res.Raw == exp)
-
-}
-
-func TestIssue141(t *testing.T) {
- json := `{"data": [{"q": 11, "w": 12}, {"q": 21, "w": 22}, {"q": 31, "w": 32} ], "sql": "some stuff here"}`
- assert(t, Get(json, "data.#").Int() == 3)
- assert(t, Get(json, "data.#.{q}|@ugly").Raw == `[{"q":11},{"q":21},{"q":31}]`)
- assert(t, Get(json, "data.#.q|@ugly").Raw == `[11,21,31]`)
-}
-
-func TestChainedModifierStringArgs(t *testing.T) {
- // issue #143
- AddModifier("push", func(json, arg string) string {
- json = strings.TrimSpace(json)
- if len(json) < 2 || !Parse(json).IsArray() {
- return json
- }
- json = strings.TrimSpace(json[1 : len(json)-1])
- if len(json) == 0 {
- return "[" + arg + "]"
- }
- return "[" + json + "," + arg + "]"
- })
- res := Get("[]", `@push:"2"|@push:"3"|@push:{"a":"b","c":["e","f"]}|@push:true|@push:10.23`)
- assert(t, res.String() == `["2","3",{"a":"b","c":["e","f"]},true,10.23]`)
-}
-
-func TestFlatten(t *testing.T) {
- json := `[1,[2],[3,4],[5,[6,[7]]],{"hi":"there"},8,[9]]`
- assert(t, Get(json, "@flatten").String() == `[1,2,3,4,5,[6,[7]],{"hi":"there"},8,9]`)
- assert(t, Get(json, `@flatten:{"deep":true}`).String() == `[1,2,3,4,5,6,7,{"hi":"there"},8,9]`)
- assert(t, Get(`{"9999":1234}`, "@flatten").String() == `{"9999":1234}`)
-}
-
-func TestJoin(t *testing.T) {
- assert(t, Get(`[{},{}]`, "@join").String() == `{}`)
- assert(t, Get(`[{"a":1},{"b":2}]`, "@join").String() == `{"a":1,"b":2}`)
- assert(t, Get(`[{"a":1,"b":1},{"b":2}]`, "@join").String() == `{"a":1,"b":2}`)
- assert(t, Get(`[{"a":1,"b":1},{"b":2},5,{"c":3}]`, "@join").String() == `{"a":1,"b":2,"c":3}`)
- assert(t, Get(`[{"a":1,"b":1},{"b":2},5,{"c":3}]`, `@join:{"preserve":true}`).String() == `{"a":1,"b":1,"b":2,"c":3}`)
- assert(t, Get(`[{"a":1,"b":1},{"b":2},5,{"c":3}]`, `@join:{"preserve":true}.b`).String() == `1`)
- assert(t, Get(`{"9999":1234}`, "@join").String() == `{"9999":1234}`)
-}
-
-func TestValid(t *testing.T) {
- assert(t, Get("[{}", "@valid").Exists() == false)
- assert(t, Get("[{}]", "@valid").Exists() == true)
-}
-
-// https://github.com/tidwall/gjson/issues/152
-func TestJoin152(t *testing.T) {
- var json = `{
- "distance": 1374.0,
- "validFrom": "2005-11-14",
- "historical": {
- "type": "Day",
- "name": "last25Hours",
- "summary": {
- "units": {
- "temperature": "C",
- "wind": "m/s",
- "snow": "cm",
- "precipitation": "mm"
- },
- "days": [
- {
- "time": "2020-02-08",
- "hours": [
- {
- "temperature": {
- "min": -2.0,
- "max": -1.6,
- "value": -1.6
- },
- "wind": {},
- "precipitation": {},
- "humidity": {
- "value": 92.0
- },
- "snow": {
- "depth": 49.0
- },
- "time": "2020-02-08T16:00:00+01:00"
- },
- {
- "temperature": {
- "min": -1.7,
- "max": -1.3,
- "value": -1.3
- },
- "wind": {},
- "precipitation": {},
- "humidity": {
- "value": 92.0
- },
- "snow": {
- "depth": 49.0
- },
- "time": "2020-02-08T17:00:00+01:00"
- },
- {
- "temperature": {
- "min": -1.3,
- "max": -0.9,
- "value": -1.2
- },
- "wind": {},
- "precipitation": {},
- "humidity": {
- "value": 91.0
- },
- "snow": {
- "depth": 49.0
- },
- "time": "2020-02-08T18:00:00+01:00"
- }
- ]
- },
- {
- "time": "2020-02-09",
- "hours": [
- {
- "temperature": {
- "min": -1.7,
- "max": -0.9,
- "value": -1.5
- },
- "wind": {},
- "precipitation": {},
- "humidity": {
- "value": 91.0
- },
- "snow": {
- "depth": 49.0
- },
- "time": "2020-02-09T00:00:00+01:00"
- },
- {
- "temperature": {
- "min": -1.5,
- "max": 0.9,
- "value": 0.2
- },
- "wind": {},
- "precipitation": {},
- "humidity": {
- "value": 67.0
- },
- "snow": {
- "depth": 49.0
- },
- "time": "2020-02-09T01:00:00+01:00"
- }
- ]
- }
- ]
- }
- }
- }`
-
- res := Get(json, "historical.summary.days.#.hours|@flatten|#.humidity.value")
- assert(t, res.Raw == `[92.0,92.0,91.0,91.0,67.0]`)
-}
diff --git a/gjson/go.mod b/gjson/go.mod
deleted file mode 100644
index d851688cc..000000000
--- a/gjson/go.mod
+++ /dev/null
@@ -1,8 +0,0 @@
-module github.com/tidwall/gjson
-
-go 1.12
-
-require (
- github.com/tidwall/match v1.0.1
- github.com/tidwall/pretty v1.0.0
-)
diff --git a/gjson/go.sum b/gjson/go.sum
deleted file mode 100644
index a4a2d872c..000000000
--- a/gjson/go.sum
+++ /dev/null
@@ -1,4 +0,0 @@
-github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc=
-github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
-github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
-github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
diff --git a/gjson/logo.png b/gjson/logo.png
deleted file mode 100644
index 17a8bbe9d..000000000
Binary files a/gjson/logo.png and /dev/null differ
diff --git a/go.mod b/go.mod
index b17db0cbb..9467a155d 100644
--- a/go.mod
+++ b/go.mod
@@ -4,8 +4,6 @@ replace github.com/lib/pq => github.com/matrix-org/pq v1.3.2
replace github.com/prometheus/client_golang => ./prometheus
-replace github.com/tidwall/gjson => ./gjson
-
require (
github.com/gorilla/mux v1.7.3
github.com/lib/pq v1.2.0
@@ -22,6 +20,7 @@ require (
github.com/pkg/errors v0.8.1
github.com/prometheus/client_golang v1.4.1
github.com/sirupsen/logrus v1.4.2
+ github.com/tidwall/gjson v1.6.0
github.com/tidwall/pretty v1.0.1 // indirect
github.com/uber/jaeger-client-go v2.22.1+incompatible
github.com/uber/jaeger-lib v2.2.0+incompatible
diff --git a/go.sum b/go.sum
index ee7554a1b..b36462637 100644
--- a/go.sum
+++ b/go.sum
@@ -88,6 +88,8 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/tidwall/gjson v1.1.5 h1:QysILxBeUEY3GTLA0fQVgkQG1zme8NxGvhh2SSqWNwI=
github.com/tidwall/gjson v1.1.5/go.mod h1:c/nTNbUr0E0OrXEhq1pwa8iEgc2DOt4ZZqAt1HtCkPA=
+github.com/tidwall/gjson v1.6.0 h1:9VEQWz6LLMUsUl6PueE49ir4Ka6CzLymOAZDxpFsTDc=
+github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc=
github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=