pax_global_header 0000666 0000000 0000000 00000000064 13771366074 0014530 g ustar 00root root 0000000 0000000 52 comment=97ec619cbe9546828dc45cb856a5938dcf3d74e1
gjson-1.6.7/ 0000775 0000000 0000000 00000000000 13771366074 0012663 5 ustar 00root root 0000000 0000000 gjson-1.6.7/.github/ 0000775 0000000 0000000 00000000000 13771366074 0014223 5 ustar 00root root 0000000 0000000 gjson-1.6.7/.github/workflows/ 0000775 0000000 0000000 00000000000 13771366074 0016260 5 ustar 00root root 0000000 0000000 gjson-1.6.7/.github/workflows/go.yml 0000664 0000000 0000000 00000001222 13771366074 0017405 0 ustar 00root root 0000000 0000000 name: Go
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.x
uses: actions/setup-go@v2
with:
go-version: ^1.13
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Get dependencies
run: |
go get -v -t -d ./...
if [ -f Gopkg.toml ]; then
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
dep ensure
fi
- name: Build
run: go build -v .
- name: Test
run: go test -v .
gjson-1.6.7/LICENSE 0000664 0000000 0000000 00000002065 13771366074 0013673 0 ustar 00root root 0000000 0000000 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.
gjson-1.6.7/README.md 0000664 0000000 0000000 00000033505 13771366074 0014150 0 ustar 00root root 0000000 0000000
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 through 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 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).
gjson-1.6.7/SYNTAX.md 0000664 0000000 0000000 00000020006 13771366074 0014231 0 ustar 00root root 0000000 0000000 # 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"
```
You'll also need to make sure that the `\` character is correctly escaped when hardcoding a path in source code.
```go
res := gjson.Get(json, "fav\\.movie") // must escape the slash
res := gjson.Get(json, `fav\.movie`) // no need to escape the slash
```
### 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"]}
```
gjson-1.6.7/gjson.go 0000664 0000000 0000000 00000167441 13771366074 0014347 0 ustar 00root root 0000000 0000000 // Package gjson provides searching for json strings.
package gjson
import (
"encoding/json"
"reflect"
"strconv"
"strings"
"time"
"unicode/utf16"
"unicode/utf8"
"unsafe"
"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:
b, _ := strconv.ParseBool(strings.ToLower(t.Str))
return b
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
i, ok := safeInt(t.Num)
if ok {
return i
}
// now try to parse the raw string
i, ok = parseInt(t.Raw)
if ok {
return i
}
// fallback to a standard conversion
return int64(t.Num)
}
}
// 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
i, ok := safeInt(t.Num)
if ok && i >= 0 {
return uint64(i)
}
// now try to parse the raw string
u, ok := parseUint(t.Raw)
if ok {
return u
}
// fallback to a standard conversion
return uint64(t.Num)
}
}
// 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 {
if i >= len(json) {
return json
}
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 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 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
}
func safeInt(f float64) (n int64, ok bool) {
if f < -9007199254740991 || f > 9007199254740991 {
return 0, false
}
return int64(f), true
}
// 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
}
// cleanWS remove any non-whitespace from string
func cleanWS(s string) string {
for i := 0; i < len(s); i++ {
switch s[i] {
case ' ', '\t', '\n', '\r':
continue
default:
var s2 []byte
for i := 0; i < len(s); i++ {
switch s[i] {
case ' ', '\t', '\n', '\r':
s2 = append(s2, s[i])
}
}
return string(s2)
}
}
return s
}
// @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 = cleanWS(value.String())
case "prefix":
opts.Prefix = cleanWS(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
}
// 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))
}
gjson-1.6.7/gjson_test.go 0000664 0000000 0000000 00000170444 13771366074 0015403 0 ustar 00root root 0000000 0000000 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: True}).Bool() == true)
assert(t, (Result{Type: False}).Bool() == false)
assert(t, (Result{Type: Number, Num: 1}).Bool() == true)
assert(t, (Result{Type: Number, Num: 0}).Bool() == false)
assert(t, (Result{Type: String, Str: "1"}).Bool() == true)
assert(t, (Result{Type: String, Str: "T"}).Bool() == true)
assert(t, (Result{Type: String, Str: "t"}).Bool() == true)
assert(t, (Result{Type: String, Str: "true"}).Bool() == true)
assert(t, (Result{Type: String, Str: "True"}).Bool() == true)
assert(t, (Result{Type: String, Str: "TRUE"}).Bool() == true)
assert(t, (Result{Type: String, Str: "tRuE"}).Bool() == true)
assert(t, (Result{Type: String, Str: "0"}).Bool() == false)
assert(t, (Result{Type: String, Str: "f"}).Bool() == false)
assert(t, (Result{Type: String, Str: "F"}).Bool() == false)
assert(t, (Result{Type: String, Str: "false"}).Bool() == false)
assert(t, (Result{Type: String, Str: "False"}).Bool() == false)
assert(t, (Result{Type: String, Str: "FALSE"}).Bool() == false)
assert(t, (Result{Type: String, Str: "fAlSe"}).Bool() == false)
assert(t, (Result{Type: String, Str: "random"}).Bool() == false)
// 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]`)
}
func TestVariousFuzz(t *testing.T) {
// Issue #192 assert(t, squash(`"000"hello`) == `"000"`)
assert(t, squash(`"000"`) == `"000"`)
assert(t, squash(`"000`) == `"000`)
assert(t, squash(`"`) == `"`)
assert(t, squash(`[000]hello`) == `[000]`)
assert(t, squash(`[000]`) == `[000]`)
assert(t, squash(`[000`) == `[000`)
assert(t, squash(`[`) == `[`)
assert(t, squash(`]`) == `]`)
testJSON := `0.#[[{}]].@valid:"000`
Get(testJSON, testJSON)
// Issue #195
testJSON = `\************************************` +
`**********{**",**,,**,**,**,**,"",**,**,**,**,**,**,**,**,**,**]`
Get(testJSON, testJSON)
// Issue #196
testJSON = `[#.@pretty.@join:{""[]""preserve"3,"][{]]]`
Get(testJSON, testJSON)
}
gjson-1.6.7/go.mod 0000664 0000000 0000000 00000000171 13771366074 0013770 0 ustar 00root root 0000000 0000000 module github.com/tidwall/gjson
go 1.12
require (
github.com/tidwall/match v1.0.3
github.com/tidwall/pretty v1.0.2
)
gjson-1.6.7/go.sum 0000664 0000000 0000000 00000000767 13771366074 0014030 0 ustar 00root root 0000000 0000000 github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc=
github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE=
github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU=
github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
gjson-1.6.7/logo.png 0000664 0000000 0000000 00000037100 13771366074 0014332 0 ustar 00root root 0000000 0000000 PNG
IHDR
S ?y tEXtSoftware Adobe ImageReadyqe<