From d2ffa058671e6eb9f1400c5ba21e63499fea6bdc Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Mon, 2 Mar 2020 10:18:04 +0000 Subject: [PATCH] Use gjson@1.6.0 as it fixes https://github.com/tidwall/gjson/issues/157 --- gjson/LICENSE | 20 - gjson/README.md | 495 -------- gjson/SYNTAX.md | 269 ----- gjson/gjson.go | 2824 ------------------------------------------- gjson/gjson_gae.go | 34 - gjson/gjson_ngae.go | 81 -- gjson/gjson_test.go | 2164 --------------------------------- gjson/go.mod | 8 - gjson/go.sum | 4 - gjson/logo.png | Bin 15936 -> 0 bytes go.mod | 3 +- go.sum | 2 + 12 files changed, 3 insertions(+), 5901 deletions(-) delete mode 100644 gjson/LICENSE delete mode 100644 gjson/README.md delete mode 100644 gjson/SYNTAX.md delete mode 100644 gjson/gjson.go delete mode 100644 gjson/gjson_gae.go delete mode 100644 gjson/gjson_ngae.go delete mode 100644 gjson/gjson_test.go delete mode 100644 gjson/go.mod delete mode 100644 gjson/go.sum delete mode 100644 gjson/logo.png 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 @@ -

-GJSON -
-Build Status -GoDoc -GJSON Playground -

- - - -

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 17a8bbe9d651e5d79cbd59a3645a152443440ad6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15936 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

Bg_)L$48&o}ack zt!HAQladuqH6nxcdZjxF6vTK*7%AV0IOX}JZ^u_OkMumMel#gIzv1VJUb|?$j71?Q zE%B-a&vFk0&Z20HLn$L5hH9dQ4Ef5bKHOX_RfA5_`gJvR%bW4}0DCGg0Va&YC>upU z1&8GC>xLY}LkxejF;yq2sOY~5Cqo4UPpDVfIk~O=P(Gb%k`up^XsPRv9)fXL9y}y5 z_nYE{jUJUly7pJ?$r_eiajL0B@N?t6efF1pS9lk2UGDMWIj>@eS3OH*kme)>qMs4c zXm_q!8hu`I8Dhv5J1?UvlYLJ*X6D(~=y^sb&mT@}>JxIp-$sTvzSdP`p2 zJSTpI%GX_eG4+h<`8LXOC{XRWB^Zd0<_&>b2eoF`!(!~j1?f~Xrey`D;63!OSo@1j zhVkNITfkbp3}EkdgcmMq1ZjCrPrG8@X$!y3nZFDG<8?k82gdmVJ<<=koN(R0uQJ&q&;Hm9we)+ z>EiW_<2L!S=W{bXQR{t-Xk&^=i3!xJ1GH*$ft^*`kFY2U5$7}?R(xpv1OE!wXhM-U z{|-m&g!{Wci5J~AR7xhAcz2%T1X}X;Lm5kqn$n>CdZG%!jlgyEgu}8cSE=Tna>KaW zE{i{1$MRasX61aWzh$Kz+gg_XXfmLI)gJjN8%-etjvKrZa0TIVu@FNzi_DBbgG}?X zc%`<^VRHLdaERnl?k6tkmy~V-6wDDz--2n{wg8GA9pEX|*>WDA)uFS%@&MN%-fruuv;9cz5hkb^M5ny{e|a>hsQrwX-D^ zNBO*f*^1b~?2NHQ2g~@Hiq$_V;Z8|Q@sz(OJ-_;@xOeF3^57a-&;bjXTR0^(9&O1`Qs5mDXKaZ{gYh~^a4>N0el@Q8ji@;G0VwtnJC=gh zkvhS5%hY0~-FeS=8*`P?hYjBB(TJ(h3vvLJ;q{z?7Jf~hQZOlh z8(M!wZ6}#fur(A{mkQvWp^(z{kJ9Qcf&hRH6HK!c5b1?W28wck^aQ{Ju^0{PD&(m< zLbu{`I17`!l>tz)uDi~j;Q*7#M!X;*rP#E#K`0MHVO=C_Y>Kpq3I%tz)KS21z(70w zu+S+efLBlwQ<3knLW3-aM&dW%uuv1r?kQqDWsn|wHc35K!jBl%t~9oLn(8w!(iF2Y zj)Ca3zC|-dT69O*c44Drzb zTWRcGB4pjuzq;tNLKJ7H7&3CTH`^5&fs^&Csh+4TdNT~n>o18P123FK1<6Ap7>9OE zH?~wjjt8TicxY-E;Ld@{^P%j9BpeC^HJlw8iW2I<*Vc#1VH5Y&k@$T23>riN5a^fE z1xtK3We+L~pTm`C@mKj&g@flbKtRjuc?i2+3nW^d516YT2Qx*|&}0%IIZ{BK)n@-A z7W$f>T8g%2qR?15Ezb7#Al1jSng=Um;<;^4CZ)UF4*B#s16}WxXDR&e;IFDemyJBP zQD>pCTMx2W)Iwg$ckc4ElkYeRp?hVt(&_O^6u-=WO4y!T>T70vx)M)1f17nmsqw{$ zZhYP=C<2j}m^Lg-ex&8RKf~0WrfrL!~NLQ^Xk?)No>Z1xiNn~MBa)Y#@)-369lm6 zDH#U3Pv&kw{mt3+_t3uW8=*{`^%?Rl0~ff@XZrjZDZCjasI5PtLP6)zBrRO$pYlfV z4DCdS&(8)uR*23AuP?jhTi@#6yL=G*b~7!5d3?2d_2DzzR3V+SVCZe@ULo|*Qz`CV z*j}+=>D|^uy$<{s0tgCLYba;t*4jWsj}CfRI?T4cS90us!fy8Lz;(qp7V}H&`+loKEpPZr6wt`2EqWMWj z6-EjjNWU@D@IQdNmlvT5>qUq{`9m 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=