Updated gjson library

This commit is contained in:
tidwall 2019-08-15 06:08:38 -07:00
parent 42c3ec047e
commit 54f14d8b03
10 changed files with 1899 additions and 265 deletions

6
Gopkg.lock generated
View File

@ -233,12 +233,12 @@
version = "v1.1.6" version = "v1.1.6"
[[projects]] [[projects]]
digest = "1:3ddca2bd5496c6922a2a9e636530e178a43c2a534ea6634211acdc7d10222794" digest = "1:30e9a79822702670b96d3461aca7da11b8cc6e7954eb4e859e886559ed4802a4"
name = "github.com/tidwall/gjson" name = "github.com/tidwall/gjson"
packages = ["."] packages = ["."]
pruneopts = "" pruneopts = ""
revision = "1e3f6aeaa5bad08d777ea7807b279a07885dd8b2" revision = "c5e72cdf74dff23857243dd662c465b810891c21"
version = "v1.1.3" version = "v1.3.2"
[[projects]] [[projects]]
branch = "master" branch = "master"

View File

@ -71,7 +71,7 @@ required = [
[[constraint]] [[constraint]]
name = "github.com/tidwall/gjson" name = "github.com/tidwall/gjson"
version = "1.0.1" version = "1.3.2"
[[constraint]] [[constraint]]
branch = "master" branch = "master"

View File

@ -55,6 +55,9 @@ Prichard
## Path Syntax ## 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 path is a series of keys separated by a dot.
A key may contain special wildcard characters '\*' and '?'. A key may contain special wildcard characters '\*' and '?'.
To access an array value use the index as the key. To access an array value use the index as the key.
@ -68,9 +71,9 @@ The dot and wildcard characters can be escaped with '\\'.
"children": ["Sara","Alex","Jack"], "children": ["Sara","Alex","Jack"],
"fav.movie": "Deer Hunter", "fav.movie": "Deer Hunter",
"friends": [ "friends": [
{"first": "Dale", "last": "Murphy", "age": 44}, {"first": "Dale", "last": "Murphy", "age": 44, "nets": ["ig", "fb", "tw"]},
{"first": "Roger", "last": "Craig", "age": 68}, {"first": "Roger", "last": "Craig", "age": 68, "nets": ["fb", "tw"]},
{"first": "Jane", "last": "Murphy", "age": 47} {"first": "Jane", "last": "Murphy", "age": 47, "nets": ["ig", "tw"]}
] ]
} }
``` ```
@ -87,45 +90,24 @@ The dot and wildcard characters can be escaped with '\\'.
"friends.1.last" >> "Craig" "friends.1.last" >> "Craig"
``` ```
You can also query an array for the first match by using `#[...]`, or find all matches with `#[...]#`. You can also query an array for the first match by using `#(...)`, or find all
Queries support the `==`, `!=`, `<`, `<=`, `>`, `>=` comparison operators and the simple pattern matching `%` operator. 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"
friends.#[last=="Murphy"]#.first >> ["Dale","Jane"] friends.#(last=="Murphy")#.first >> ["Dale","Jane"]
friends.#[age>45]#.last >> ["Craig","Murphy"] friends.#(age>45)#.last >> ["Craig","Murphy"]
friends.#[first%"D*"].last >> "Murphy" friends.#(first%"D*").last >> "Murphy"
friends.#(first!%"D*").last >> "Craig"
friends.#(nets.#(=="fb"))#.first >> ["Dale","Roger"]
``` ```
## JSON Lines *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
There's support for [JSON Lines](http://jsonlines.org/) using the `..` prefix, which treats a multilined document as an array. [multipath](SYNTAX.md#multipaths) syntax. For backwards compatibility,
`#[...]` will continue to work until the next major release.*
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
})
```
## Result Type ## Result Type
@ -193,6 +175,114 @@ result.Int() int64 // -9223372036854775808 to 9223372036854775807
result.Uint() int64 // 0 to 18446744073709551615 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 three 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.
### 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 ## Get nested array values
Suppose you want all the last names from the following json: Suppose you want all the last names from the following json:
@ -226,7 +316,7 @@ for _, name := range result.Array() {
You can also query an object inside an array: You can also query an object inside an array:
```go ```go
name := gjson.Get(json, `programmers.#[lastName="Hunter"].firstName`) name := gjson.Get(json, `programmers.#(lastName="Hunter").firstName`)
println(name.String()) // prints "Elliotte" println(name.String()) // prints "Elliotte"
``` ```

264
vendor/github.com/tidwall/gjson/SYNTAX.md generated vendored Normal file
View File

@ -0,0 +1,264 @@
# 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)
The definitive implemenation is [github.com/tidwall/gjson](https://github.com/tidwall/gjson).
Use the [GJSON Playground](https://gjson.dev) to experiment with the syntax online.
## Path structure
A GJSON Path is intended to be easily expressed as a series of components seperated by a `.` character.
Along with `.` character, there are a few more that have special meaning, including `|`, `#`, `@`, `\`, `*`, and `?`.
## Example
Given this JSON
```json
{
"name": {"first": "Tom", "last": "Anderson"},
"age":37,
"children": ["Sara","Alex","Jack"],
"fav.movie": "Deer Hunter",
"friends": [
{"first": "Dale", "last": "Murphy", "age": 44, "nets": ["ig", "fb", "tw"]},
{"first": "Roger", "last": "Craig", "age": 68, "nets": ["fb", "tw"]},
{"first": "Jane", "last": "Murphy", "age": 47, "nets": ["ig", "tw"]}
]
}
```
The following GJSON Paths evaluate to the accompanying values.
### Basic
In many cases you'll just want to retreive values by object name or array index.
```go
name.last "Anderson"
name.first "Tom"
age 37
children ["Sara","Alex","Jack"]
children.0 "Sara"
children.1 "Alex"
friends.1 {"first": "Roger", "last": "Craig", "age": 68}
friends.1.first "Roger"
```
### Wildcards
A key may contain the special wildcard characters `*` and `?`.
The `*` will match on any zero+ characters, and `?` matches on any one character.
```go
child*.2 "Jack"
c?ildren.0 "Sara"
```
### Escape character
Special purpose characters, such as `.`, `*`, and `?` can be escaped with `\`.
```go
fav\.movie "Deer Hunter"
```
### Arrays
The `#` character allows for digging into JSON Arrays.
To get the length of an array you'll just use the `#` all by itself.
```go
friends.# 3
friends.#.age [44,68,47]
```
### Queries
You can also query an array for the first match by using `#(...)`, or find all matches with `#(...)#`.
Queries support the `==`, `!=`, `<`, `<=`, `>`, `>=` comparison operators,
and the simple pattern matching `%` (like) and `!%` (not like) operators.
```go
friends.#(last=="Murphy").first "Dale"
friends.#(last=="Murphy")#.first ["Dale","Jane"]
friends.#(age>45)#.last ["Craig","Murphy"]
friends.#(first%"D*").last "Murphy"
friends.#(first!%"D*").last "Craig"
```
To query for a non-object value in an array, you can forgo the string to the right of the operator.
```go
children.#(!%"*a*") "Alex"
children.#(%"*a*")# ["Sara","Jack"]
```
Nested queries are allowed.
```go
friends.#(nets.#(=="fb"))#.first >> ["Dale","Roger"]
```
*Please note that prior to v1.3.0, queries used the `#[...]` brackets. This was
changed in v1.3.0 as to avoid confusion with the new [multipath](#multipaths)
syntax. For backwards compatibility, `#[...]` will continue to work until the
next major release.*
### Dot vs Pipe
The `.` is standard separator, but it's also possible to use a `|`.
In most cases they both end up returning the same results.
The cases where`|` differs from `.` is when it's used after the `#` for [Arrays](#arrays) and [Queries](#queries).
Here are some examples
```go
friends.0.first "Dale"
friends|0.first "Dale"
friends.0|first "Dale"
friends|0|first "Dale"
friends|# 3
friends.# 3
friends.#(last="Murphy")# [{"first": "Dale", "last": "Murphy", "age": 44},{"first": "Jane", "last": "Murphy", "age": 47}]
friends.#(last="Murphy")#.first ["Dale","Jane"]
friends.#(last="Murphy")#|first <non-existent>
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 three 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.
#### 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"]}
```

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
//+build appengine //+build appengine js
package gjson package gjson
@ -8,3 +8,11 @@ func getBytes(json []byte, path string) Result {
func fillIndex(json string, c *parseContext) { func fillIndex(json string, c *parseContext) {
// noop. Use zero for the Index value. // noop. Use zero for the Index value.
} }
func stringBytes(s string) []byte {
return []byte(s)
}
func bytesString(b []byte) string {
return string(b)
}

View File

@ -1,4 +1,5 @@
//+build !appengine //+build !appengine
//+build !js
package gjson package gjson
@ -15,45 +16,40 @@ func getBytes(json []byte, path string) Result {
if json != nil { if json != nil {
// unsafe cast to string // unsafe cast to string
result = Get(*(*string)(unsafe.Pointer(&json)), path) result = Get(*(*string)(unsafe.Pointer(&json)), path)
result = fromBytesGet(result) // safely get the string headers
} rawhi := *(*reflect.StringHeader)(unsafe.Pointer(&result.Raw))
return result strhi := *(*reflect.StringHeader)(unsafe.Pointer(&result.Str))
} // create byte slice headers
rawh := reflect.SliceHeader{Data: rawhi.Data, Len: rawhi.Len}
func fromBytesGet(result Result) Result { strh := reflect.SliceHeader{Data: strhi.Data, Len: strhi.Len}
// safely get the string headers if strh.Data == 0 {
rawhi := *(*reflect.StringHeader)(unsafe.Pointer(&result.Raw)) // str is nil
strhi := *(*reflect.StringHeader)(unsafe.Pointer(&result.Str)) if rawh.Data == 0 {
// create byte slice headers // raw is nil
rawh := reflect.SliceHeader{Data: rawhi.Data, Len: rawhi.Len} result.Raw = ""
strh := reflect.SliceHeader{Data: strhi.Data, Len: strhi.Len} } else {
if strh.Data == 0 { // raw has data, safely copy the slice header to a string
// str is nil result.Raw = string(*(*[]byte)(unsafe.Pointer(&rawh)))
if rawh.Data == 0 { }
result.Str = ""
} else if rawh.Data == 0 {
// raw is nil // raw is nil
result.Raw = "" result.Raw = ""
} else { // str has data, safely copy the slice header to a string
// raw 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))) 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)))
} }
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 return result
} }
@ -71,3 +67,15 @@ func fillIndex(json string, c *parseContext) {
} }
} }
} }
func stringBytes(s string) []byte {
return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
Data: (*reflect.StringHeader)(unsafe.Pointer(&s)).Data,
Len: len(s),
Cap: len(s),
}))
}
func bytesString(b []byte) string {
return *(*string)(unsafe.Pointer(&b))
}

View File

@ -11,6 +11,8 @@ import (
"strings" "strings"
"testing" "testing"
"time" "time"
"github.com/tidwall/pretty"
) )
// TestRandomData is a fuzzing test that throws random data at the Parse // TestRandomData is a fuzzing test that throws random data at the Parse
@ -55,14 +57,16 @@ func TestRandomValidStrings(t *testing.T) {
} }
token := Get(`{"str":`+string(sm)+`}`, "str") token := Get(`{"str":`+string(sm)+`}`, "str")
if token.Type != String || token.Str != su { if token.Type != String || token.Str != su {
println("["+token.Raw+"]", "["+token.Str+"]", "["+su+"]", "["+string(sm)+"]") println("["+token.Raw+"]", "["+token.Str+"]", "["+su+"]",
"["+string(sm)+"]")
t.Fatal("string mismatch") t.Fatal("string mismatch")
} }
} }
} }
func TestEmoji(t *testing.T) { func TestEmoji(t *testing.T) {
const input = `{"utf8":"Example emoji, KO: \ud83d\udd13, \ud83c\udfc3 OK: \u2764\ufe0f "}` const input = `{"utf8":"Example emoji, KO: \ud83d\udd13, \ud83c\udfc3 ` +
`OK: \u2764\ufe0f "}`
value := Get(input, "utf8") value := Get(input, "utf8")
var s string var s string
json.Unmarshal([]byte(value.Raw), &s) json.Unmarshal([]byte(value.Raw), &s)
@ -142,18 +146,21 @@ var basicJSON = `{"age":100, "name":{"here":"B\\\"R"},
var basicJSONB = []byte(basicJSON) var basicJSONB = []byte(basicJSON)
func TestTimeResult(t *testing.T) { func TestTimeResult(t *testing.T) {
assert(t, Get(basicJSON, "created").String() == Get(basicJSON, "created").Time().Format(time.RFC3339Nano)) assert(t, Get(basicJSON, "created").String() ==
Get(basicJSON, "created").Time().Format(time.RFC3339Nano))
} }
func TestParseAny(t *testing.T) { func TestParseAny(t *testing.T) {
assert(t, Parse("100").Float() == 100) assert(t, Parse("100").Float() == 100)
assert(t, Parse("true").Bool()) assert(t, Parse("true").Bool())
assert(t, Parse("false").Bool() == false) assert(t, Parse("false").Bool() == false)
assert(t, Parse("yikes").Exists() == false)
} }
func TestManyVariousPathCounts(t *testing.T) { func TestManyVariousPathCounts(t *testing.T) {
json := `{"a":"a","b":"b","c":"c"}` json := `{"a":"a","b":"b","c":"c"}`
counts := []int{3, 4, 7, 8, 9, 15, 16, 17, 31, 32, 33, 63, 64, 65, 127, 128, 129, 255, 256, 257, 511, 512, 513} counts := []int{3, 4, 7, 8, 9, 15, 16, 17, 31, 32, 33, 63, 64, 65, 127,
128, 129, 255, 256, 257, 511, 512, 513}
paths := []string{"a", "b", "c"} paths := []string{"a", "b", "c"}
expects := []string{"a", "b", "c"} expects := []string{"a", "b", "c"}
for _, count := range counts { for _, count := range counts {
@ -171,7 +178,8 @@ func TestManyVariousPathCounts(t *testing.T) {
results := GetMany(json, gpaths...) results := GetMany(json, gpaths...)
for i := 0; i < len(paths); i++ { for i := 0; i < len(paths); i++ {
if results[i].String() != expects[i] { if results[i].String() != expects[i] {
t.Fatalf("expected '%v', got '%v'", expects[i], results[i].String()) t.Fatalf("expected '%v', got '%v'", expects[i],
results[i].String())
} }
} }
} }
@ -347,7 +355,8 @@ func TestForEach(t *testing.T) {
} }
func TestMap(t *testing.T) { func TestMap(t *testing.T) {
assert(t, len(ParseBytes([]byte(`"asdf"`)).Map()) == 0) assert(t, len(ParseBytes([]byte(`"asdf"`)).Map()) == 0)
assert(t, ParseBytes([]byte(`{"asdf":"ghjk"`)).Map()["asdf"].String() == "ghjk") assert(t, ParseBytes([]byte(`{"asdf":"ghjk"`)).Map()["asdf"].String() ==
"ghjk")
assert(t, len(Result{Type: JSON, Raw: "**invalid**"}.Map()) == 0) 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: "**invalid**"}.Value() == nil)
assert(t, Result{Type: JSON, Raw: "{"}.Map() != nil) assert(t, Result{Type: JSON, Raw: "{"}.Map() != nil)
@ -368,16 +377,21 @@ func TestBasic1(t *testing.T) {
value.ForEach(func(key, value Result) bool { value.ForEach(func(key, value Result) bool {
switch i { switch i {
case 0: case 0:
if key.String() != "firstName" || value.String() != "Brett" { if key.String() != "firstName" ||
t.Fatalf("expected %v/%v got %v/%v", "firstName", "Brett", key.String(), value.String()) value.String() != "Brett" {
t.Fatalf("expected %v/%v got %v/%v", "firstName",
"Brett", key.String(), value.String())
} }
case 1: case 1:
if key.String() != "lastName" || value.String() != "McLaughlin" { if key.String() != "lastName" ||
t.Fatalf("expected %v/%v got %v/%v", "lastName", "McLaughlin", key.String(), value.String()) value.String() != "McLaughlin" {
t.Fatalf("expected %v/%v got %v/%v", "lastName",
"McLaughlin", key.String(), value.String())
} }
case 2: case 2:
if key.String() != "email" || value.String() != "aaaa" { if key.String() != "email" || value.String() != "aaaa" {
t.Fatalf("expected %v/%v got %v/%v", "email", "aaaa", key.String(), value.String()) t.Fatalf("expected %v/%v got %v/%v", "email", "aaaa",
key.String(), value.String())
} }
} }
i++ i++
@ -395,7 +409,8 @@ func TestBasic2(t *testing.T) {
if mtok.String() != "1002.3" { if mtok.String() != "1002.3" {
t.Fatalf("expected %v, got %v", "1002.3", mtok.String()) t.Fatalf("expected %v, got %v", "1002.3", mtok.String())
} }
mtok = get(basicJSON, `loggy.programmers.#[firstName != "Brett"].firstName`) mtok = get(basicJSON,
`loggy.programmers.#[firstName != "Brett"].firstName`)
if mtok.String() != "Jason" { if mtok.String() != "Jason" {
t.Fatalf("expected %v, got %v", "Jason", mtok.String()) t.Fatalf("expected %v, got %v", "Jason", mtok.String())
} }
@ -403,6 +418,10 @@ func TestBasic2(t *testing.T) {
if mtok.String() != "aaaa" { if mtok.String() != "aaaa" {
t.Fatalf("expected %v, got %v", "aaaa", mtok.String()) 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`) mtok = get(basicJSON, `loggy.programmers.#[firstName == "Brett"].email`)
if mtok.String() != "aaaa" { if mtok.String() != "aaaa" {
t.Fatalf("expected %v, got %v", "aaaa", mtok.String()) t.Fatalf("expected %v, got %v", "aaaa", mtok.String())
@ -416,13 +435,16 @@ func TestBasic2(t *testing.T) {
} }
programmers := mtok.Map()["programmers"] programmers := mtok.Map()["programmers"]
if programmers.Array()[1].Map()["firstName"].Str != "Jason" { if programmers.Array()[1].Map()["firstName"].Str != "Jason" {
t.Fatalf("expected %v, got %v", "Jason", mtok.Map()["programmers"].Array()[1].Map()["firstName"].Str) t.Fatalf("expected %v, got %v", "Jason",
mtok.Map()["programmers"].Array()[1].Map()["firstName"].Str)
} }
} }
func TestBasic3(t *testing.T) { func TestBasic3(t *testing.T) {
var mtok Result var mtok Result
if Parse(basicJSON).Get("loggy.programmers").Get("1").Get("firstName").Str != "Jason" { if Parse(basicJSON).Get("loggy.programmers").Get("1").
t.Fatalf("expected %v, got %v", "Jason", Parse(basicJSON).Get("loggy.programmers").Get("1").Get("firstName").Str) Get("firstName").Str != "Jason" {
t.Fatalf("expected %v, got %v", "Jason", Parse(basicJSON).
Get("loggy.programmers").Get("1").Get("firstName").Str)
} }
var token Result var token Result
if token = Parse("-102"); token.Num != -102 { if token = Parse("-102"); token.Num != -102 {
@ -462,7 +484,8 @@ func TestBasic4(t *testing.T) {
t.Fatalf("expected 3, got %v", get(basicJSON, "items.3.tags.#").Num) t.Fatalf("expected 3, got %v", get(basicJSON, "items.3.tags.#").Num)
} }
if get(basicJSON, "items.3.points.1.#").Num != 2 { if get(basicJSON, "items.3.points.1.#").Num != 2 {
t.Fatalf("expected 2, got %v", get(basicJSON, "items.3.points.1.#").Num) t.Fatalf("expected 2, got %v",
get(basicJSON, "items.3.points.1.#").Num)
} }
if get(basicJSON, "items.#").Num != 8 { if get(basicJSON, "items.#").Num != 8 {
t.Fatalf("expected 6, got %v", get(basicJSON, "items.#").Num) t.Fatalf("expected 6, got %v", get(basicJSON, "items.#").Num)
@ -518,7 +541,8 @@ func TestBasic5(t *testing.T) {
_ = token.Value().(bool) _ = token.Value().(bool)
token = get(basicJSON, "noop") token = get(basicJSON, "noop")
if token.String() != `{"what is a wren?":"a bird"}` { if token.String() != `{"what is a wren?":"a bird"}` {
t.Fatal("expecting '"+`{"what is a wren?":"a bird"}`+"'", "got", token.String()) t.Fatal("expecting '"+`{"what is a wren?":"a bird"}`+"'", "got",
token.String())
} }
_ = token.Value().(map[string]interface{}) _ = token.Value().(map[string]interface{})
@ -528,8 +552,10 @@ func TestBasic5(t *testing.T) {
get(basicJSON, "vals.hello") get(basicJSON, "vals.hello")
mm := Parse(basicJSON).Value().(map[string]interface{}) type msi = map[string]interface{}
fn := mm["loggy"].(map[string]interface{})["programmers"].([]interface{})[1].(map[string]interface{})["firstName"].(string) type fi = []interface{}
mm := Parse(basicJSON).Value().(msi)
fn := mm["loggy"].(msi)["programmers"].(fi)[1].(msi)["firstName"].(string)
if fn != "Jason" { if fn != "Jason" {
t.Fatalf("expecting %v, got %v", "Jason", fn) t.Fatalf("expecting %v, got %v", "Jason", fn)
} }
@ -577,12 +603,18 @@ func TestLess(t *testing.T) {
assert(t, Result{Type: Null}.Less(Result{Type: String}, 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: Null}, true))
assert(t, Result{Type: False}.Less(Result{Type: True}, 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,
assert(t, Result{Type: String, Str: "ABC"}.Less(Result{Type: String, Str: "abc"}, true)) Str: "bcd"}, true))
assert(t, !Result{Type: String, Str: "ABC"}.Less(Result{Type: String, Str: "abc"}, false)) assert(t, Result{Type: String, Str: "ABC"}.Less(Result{Type: String,
assert(t, Result{Type: Number, Num: 123}.Less(Result{Type: Number, Num: 456}, true)) Str: "abc"}, true))
assert(t, !Result{Type: Number, Num: 456}.Less(Result{Type: Number, Num: 123}, true)) assert(t, !Result{Type: String, Str: "ABC"}.Less(Result{Type: String,
assert(t, !Result{Type: Number, Num: 456}.Less(Result{Type: Number, Num: 456}, true)) 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("Abcde", "BBCDE")) assert(t, stringLessInsensitive("Abcde", "BBCDE"))
@ -764,7 +796,8 @@ func TestManyBasic(t *testing.T) {
testMany(false, "[Point]", "position.type") testMany(false, "[Point]", "position.type")
testMany(false, `[emptya ["world peace"] 31]`, ".a", "loves", "age") testMany(false, `[emptya ["world peace"] 31]`, ".a", "loves", "age")
testMany(false, `[["world peace"]]`, "loves") testMany(false, `[["world peace"]]`, "loves")
testMany(false, `[{"last":"Anderson","first":"Nancy"} Nancy]`, "name", "name.first") testMany(false, `[{"last":"Anderson","first":"Nancy"} Nancy]`, "name",
"name.first")
testMany(true, `[]`, strings.Repeat("a.", 40)+"hello") testMany(true, `[]`, strings.Repeat("a.", 40)+"hello")
res := Get(manyJSON, strings.Repeat("a.", 48)+"a") res := Get(manyJSON, strings.Repeat("a.", 48)+"a")
testMany(true, `[`+res.String()+`]`, strings.Repeat("a.", 48)+"a") testMany(true, `[`+res.String()+`]`, strings.Repeat("a.", 48)+"a")
@ -776,7 +809,8 @@ func testMany(t *testing.T, json string, paths, expected []string) {
testManyAny(t, json, paths, expected, true) testManyAny(t, json, paths, expected, true)
testManyAny(t, json, paths, expected, false) testManyAny(t, json, paths, expected, false)
} }
func testManyAny(t *testing.T, json string, paths, expected []string, bytes bool) { func testManyAny(t *testing.T, json string, paths, expected []string,
bytes bool) {
var result []Result var result []Result
for i := 0; i < 2; i++ { for i := 0; i < 2; i++ {
var which string var which string
@ -800,16 +834,21 @@ func testManyAny(t *testing.T, json string, paths, expected []string, bytes bool
} }
for j := 0; j < len(expected); j++ { for j := 0; j < len(expected); j++ {
if result[j].String() != 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()) t.Fatalf("Using key '%s' for '%s'\nexpected '%v', got '%v'",
paths[j], which, expected[j], result[j].String())
} }
} }
} }
} }
func TestIssue20(t *testing.T) { func TestIssue20(t *testing.T) {
json := `{ "name": "FirstName", "name1": "FirstName1", "address": "address1", "addressDetails": "address2", }` json := `{ "name": "FirstName", "name1": "FirstName1", ` +
`"address": "address1", "addressDetails": "address2", }`
paths := []string{"name", "name1", "address", "addressDetails"} paths := []string{"name", "name1", "address", "addressDetails"}
expected := []string{"FirstName", "FirstName1", "address1", "address2"} expected := []string{"FirstName", "FirstName1", "address1", "address2"}
t.Run("SingleMany", func(t *testing.T) { testMany(t, json, paths, expected) }) t.Run("SingleMany", func(t *testing.T) {
testMany(t, json, paths,
expected)
})
} }
func TestIssue21(t *testing.T) { func TestIssue21(t *testing.T) {
@ -817,9 +856,14 @@ func TestIssue21(t *testing.T) {
"Level1Field4":4, "Level1Field4":4,
"Level1Field2":{ "Level2Field1":[ "value1", "value2" ], "Level1Field2":{ "Level2Field1":[ "value1", "value2" ],
"Level2Field2":{ "Level3Field1":[ { "key1":"value1" } ] } } }` "Level2Field2":{ "Level3Field1":[ { "key1":"value1" } ] } } }`
paths := []string{"Level1Field1", "Level1Field2.Level2Field1", "Level1Field2.Level2Field2.Level3Field1", "Level1Field4"} paths := []string{"Level1Field1", "Level1Field2.Level2Field1",
expected := []string{"3", `[ "value1", "value2" ]`, `[ { "key1":"value1" } ]`, "4"} "Level1Field2.Level2Field2.Level3Field1", "Level1Field4"}
t.Run("SingleMany", func(t *testing.T) { testMany(t, json, paths, expected) }) 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) { func TestRandomMany(t *testing.T) {
@ -965,7 +1009,8 @@ func TestUnmarshal(t *testing.T) {
t.Fatal("not equal") t.Fatal("not equal")
} }
var str string var str string
if err := json.Unmarshal([]byte(Get(complicatedJSON, "LeftOut").Raw), &str); err != nil { err := json.Unmarshal([]byte(Get(complicatedJSON, "LeftOut").Raw), &str)
if err != nil {
t.Fatal(err) t.Fatal(err)
} }
assert(t, str == Get(complicatedJSON, "LeftOut").String()) assert(t, str == Get(complicatedJSON, "LeftOut").String())
@ -1034,7 +1079,8 @@ func TestValidBasic(t *testing.T) {
testvalid(t, `{"a":"b","a":1}`, true) testvalid(t, `{"a":"b","a":1}`, true)
testvalid(t, `{"a":"b",2"1":2}`, false) 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"} }`, true)
testvalid(t, `{"a":"b","a": 1, "c":{"hi":"there", "easy":["going",{"mixed":"bag"}]} }`, true) testvalid(t, `{"a":"b","a": 1, "c":{"hi":"there", "easy":["going",`+
`{"mixed":"bag"}]} }`, true)
testvalid(t, `""`, true) testvalid(t, `""`, true)
testvalid(t, `"`, false) testvalid(t, `"`, false)
testvalid(t, `"\n"`, true) testvalid(t, `"\n"`, true)
@ -1049,7 +1095,8 @@ func TestValidBasic(t *testing.T) {
testvalid(t, string(exampleJSON), true) testvalid(t, string(exampleJSON), true)
} }
var jsonchars = []string{"{", "[", ",", ":", "}", "]", "1", "0", "true", "false", "null", `""`, `"\""`, `"a"`} var jsonchars = []string{"{", "[", ",", ":", "}", "]", "1", "0", "true",
"false", "null", `""`, `"\""`, `"a"`}
func makeRandomJSONChars(b []byte) { func makeRandomJSONChars(b []byte) {
var bb []byte var bb []byte
@ -1058,6 +1105,7 @@ func makeRandomJSONChars(b []byte) {
} }
copy(b, bb[:len(b)]) copy(b, bb[:len(b)])
} }
func TestValidRandom(t *testing.T) { func TestValidRandom(t *testing.T) {
rand.Seed(time.Now().UnixNano()) rand.Seed(time.Now().UnixNano())
b := make([]byte, 100000) b := make([]byte, 100000)
@ -1077,7 +1125,8 @@ func TestValidRandom(t *testing.T) {
} }
func TestGetMany47(t *testing.T) { func TestGetMany47(t *testing.T) {
json := `{"bar": {"id": 99, "mybar": "my mybar" }, "foo": {"myfoo": [605]}}` json := `{"bar": {"id": 99, "mybar": "my mybar" }, "foo": ` +
`{"myfoo": [605]}}`
paths := []string{"foo.myfoo", "bar.id", "bar.mybar", "bar.mybarx"} paths := []string{"foo.myfoo", "bar.id", "bar.mybar", "bar.mybarx"}
expected := []string{"[605]", "99", "my mybar", ""} expected := []string{"[605]", "99", "my mybar", ""}
results := GetMany(json, paths...) results := GetMany(json, paths...)
@ -1086,7 +1135,8 @@ func TestGetMany47(t *testing.T) {
} }
for i, path := range paths { for i, path := range paths {
if results[i].String() != expected[i] { if results[i].String() != expected[i] {
t.Fatalf("expected '%v', got '%v' for path '%v'", expected[i], results[i].String(), path) t.Fatalf("expected '%v', got '%v' for path '%v'", expected[i],
results[i].String(), path)
} }
} }
} }
@ -1101,7 +1151,8 @@ func TestGetMany48(t *testing.T) {
} }
for i, path := range paths { for i, path := range paths {
if results[i].String() != expected[i] { if results[i].String() != expected[i] {
t.Fatalf("expected '%v', got '%v' for path '%v'", expected[i], results[i].String(), path) t.Fatalf("expected '%v', got '%v' for path '%v'", expected[i],
results[i].String(), path)
} }
} }
} }
@ -1134,12 +1185,12 @@ func TestNullArray(t *testing.T) {
} }
} }
func TestRandomGetMany(t *testing.T) { // func TestRandomGetMany(t *testing.T) {
start := time.Now() // start := time.Now()
for time.Since(start) < time.Second*3 { // for time.Since(start) < time.Second*3 {
testRandomGetMany(t) // testRandomGetMany(t)
} // }
} // }
func testRandomGetMany(t *testing.T) { func testRandomGetMany(t *testing.T) {
rand.Seed(time.Now().UnixNano()) rand.Seed(time.Now().UnixNano())
json, keys := randomJSON() json, keys := randomJSON()
@ -1183,15 +1234,18 @@ func TestIssue54(t *testing.T) {
json := `{"MarketName":null,"Nounce":6115}` json := `{"MarketName":null,"Nounce":6115}`
r = GetMany(json, "Nounce", "Buys", "Sells", "Fills") r = GetMany(json, "Nounce", "Buys", "Sells", "Fills")
if strings.Replace(fmt.Sprintf("%v", r), " ", "", -1) != "[6115]" { if strings.Replace(fmt.Sprintf("%v", r), " ", "", -1) != "[6115]" {
t.Fatalf("expected '%v', got '%v'", "[6115]", strings.Replace(fmt.Sprintf("%v", r), " ", "", -1)) t.Fatalf("expected '%v', got '%v'", "[6115]",
strings.Replace(fmt.Sprintf("%v", r), " ", "", -1))
} }
r = GetMany(json, "Nounce", "Buys", "Sells") r = GetMany(json, "Nounce", "Buys", "Sells")
if strings.Replace(fmt.Sprintf("%v", r), " ", "", -1) != "[6115]" { if strings.Replace(fmt.Sprintf("%v", r), " ", "", -1) != "[6115]" {
t.Fatalf("expected '%v', got '%v'", "[6115]", strings.Replace(fmt.Sprintf("%v", r), " ", "", -1)) t.Fatalf("expected '%v', got '%v'", "[6115]",
strings.Replace(fmt.Sprintf("%v", r), " ", "", -1))
} }
r = GetMany(json, "Nounce") r = GetMany(json, "Nounce")
if strings.Replace(fmt.Sprintf("%v", r), " ", "", -1) != "[6115]" { if strings.Replace(fmt.Sprintf("%v", r), " ", "", -1) != "[6115]" {
t.Fatalf("expected '%v', got '%v'", "[6115]", strings.Replace(fmt.Sprintf("%v", r), " ", "", -1)) t.Fatalf("expected '%v', got '%v'", "[6115]",
strings.Replace(fmt.Sprintf("%v", r), " ", "", -1))
} }
} }
@ -1222,7 +1276,8 @@ func randomNumber() string {
return strconv.FormatInt(int64(rand.Int()%1000000), 10) return strconv.FormatInt(int64(rand.Int()%1000000), 10)
} }
func randomObjectOrArray(keys []string, prefix string, array bool, depth int) (string, []string) { func randomObjectOrArray(keys []string, prefix string, array bool, depth int) (
string, []string) {
N := 5 + rand.Int()%5 N := 5 + rand.Int()%5
var json string var json string
if array { if array {
@ -1357,7 +1412,7 @@ null
} }
func TestNumUint64String(t *testing.T) { func TestNumUint64String(t *testing.T) {
i := 9007199254740993 //2^53 + 1 var i int64 = 9007199254740993 //2^53 + 1
j := fmt.Sprintf(`{"data": [ %d, "hello" ] }`, i) j := fmt.Sprintf(`{"data": [ %d, "hello" ] }`, i)
res := Get(j, "data.0") res := Get(j, "data.0")
if res.String() != "9007199254740993" { if res.String() != "9007199254740993" {
@ -1366,7 +1421,7 @@ func TestNumUint64String(t *testing.T) {
} }
func TestNumInt64String(t *testing.T) { func TestNumInt64String(t *testing.T) {
i := -9007199254740993 var i int64 = -9007199254740993
j := fmt.Sprintf(`{"data":[ "hello", %d ]}`, i) j := fmt.Sprintf(`{"data":[ "hello", %d ]}`, i)
res := Get(j, "data.1") res := Get(j, "data.1")
if res.String() != "-9007199254740993" { if res.String() != "-9007199254740993" {
@ -1379,12 +1434,13 @@ func TestNumBigString(t *testing.T) {
j := fmt.Sprintf(`{"data":[ "hello", "%s" ]}`, i) j := fmt.Sprintf(`{"data":[ "hello", "%s" ]}`, i)
res := Get(j, "data.1") res := Get(j, "data.1")
if res.String() != "900719925474099301239109123101" { if res.String() != "900719925474099301239109123101" {
t.Fatalf("expected '%v', got '%v'", "900719925474099301239109123101", res.String()) t.Fatalf("expected '%v', got '%v'", "900719925474099301239109123101",
res.String())
} }
} }
func TestNumFloatString(t *testing.T) { func TestNumFloatString(t *testing.T) {
i := -9007199254740993 var i int64 = -9007199254740993
j := fmt.Sprintf(`{"data":[ "hello", %d ]}`, i) //No quotes around value!! j := fmt.Sprintf(`{"data":[ "hello", %d ]}`, i) //No quotes around value!!
res := Get(j, "data.1") res := Get(j, "data.1")
if res.String() != "-9007199254740993" { if res.String() != "-9007199254740993" {
@ -1418,8 +1474,10 @@ func TestArrayValues(t *testing.T) {
output += fmt.Sprintf("%#v", val) output += fmt.Sprintf("%#v", val)
} }
expect := strings.Join([]string{ expect := strings.Join([]string{
`gjson.Result{Type:3, Raw:"\"PERSON1\"", Str:"PERSON1", Num:0, Index:0}`, `gjson.Result{Type:3, Raw:"\"PERSON1\"", Str:"PERSON1", Num:0, ` +
`gjson.Result{Type:3, Raw:"\"PERSON2\"", Str:"PERSON2", Num:0, Index: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}`, `gjson.Result{Type:2, Raw:"0", Str:"", Num:0, Index:0}`,
}, "\n") }, "\n")
if output != expect { if output != expect {
@ -1427,3 +1485,508 @@ func TestArrayValues(t *testing.T) {
} }
} }
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)
}
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*.#[cust2<false].first`).Exists())
assert(t, Get(json, `i*.f*.#[cust2<=false].first`).Exists())
}
func TestQueryArrayValues(t *testing.T) {
json := `{
"artists": [
["Bob Dylan"],
"John Lennon",
"Mick Jagger",
"Elton John",
"Michael Jackson",
"John Smith",
true,
123,
456,
false,
null
]
}`
assert(t, Get(json, `a*.#[0="Bob Dylan"]#|#`).String() == "1")
assert(t, Get(json, `a*.#[0="Bob Dylan 2"]#|#`).String() == "0")
assert(t, Get(json, `a*.#[%"John*"]#|#`).String() == "2")
assert(t, Get(json, `a*.#[_%"John*"]#|#`).String() == "0")
assert(t, Get(json, `a*.#[="123"]#|#`).String() == "1")
}
func TestParenQueries(t *testing.T) {
json := `{
"friends": [{"a":10},{"a":20},{"a":30},{"a":40}]
}`
assert(t, Get(json, "friends.#(a>9)#|#").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")
}

8
vendor/github.com/tidwall/gjson/go.mod generated vendored Normal file
View File

@ -0,0 +1,8 @@
module github.com/tidwall/gjson
go 1.12
require (
github.com/tidwall/match v1.0.1
github.com/tidwall/pretty v1.0.0
)

4
vendor/github.com/tidwall/gjson/go.sum generated vendored Normal file
View File

@ -0,0 +1,4 @@
github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc=
github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=