mirror of https://github.com/tidwall/tile38.git
Updated gjson library
This commit is contained in:
parent
42c3ec047e
commit
54f14d8b03
|
@ -233,12 +233,12 @@
|
|||
version = "v1.1.6"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:3ddca2bd5496c6922a2a9e636530e178a43c2a534ea6634211acdc7d10222794"
|
||||
digest = "1:30e9a79822702670b96d3461aca7da11b8cc6e7954eb4e859e886559ed4802a4"
|
||||
name = "github.com/tidwall/gjson"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "1e3f6aeaa5bad08d777ea7807b279a07885dd8b2"
|
||||
version = "v1.1.3"
|
||||
revision = "c5e72cdf74dff23857243dd662c465b810891c21"
|
||||
version = "v1.3.2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
|
|
|
@ -71,7 +71,7 @@ required = [
|
|||
|
||||
[[constraint]]
|
||||
name = "github.com/tidwall/gjson"
|
||||
version = "1.0.1"
|
||||
version = "1.3.2"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
|
|
|
@ -55,6 +55,9 @@ Prichard
|
|||
|
||||
## 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.
|
||||
|
@ -68,9 +71,9 @@ The dot and wildcard characters can be escaped with '\\'.
|
|||
"children": ["Sara","Alex","Jack"],
|
||||
"fav.movie": "Deer Hunter",
|
||||
"friends": [
|
||||
{"first": "Dale", "last": "Murphy", "age": 44},
|
||||
{"first": "Roger", "last": "Craig", "age": 68},
|
||||
{"first": "Jane", "last": "Murphy", "age": 47}
|
||||
{"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"]}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
@ -87,45 +90,24 @@ The dot and wildcard characters can be escaped with '\\'.
|
|||
"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 `%` operator.
|
||||
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.#(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"]
|
||||
```
|
||||
|
||||
## 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
|
||||
})
|
||||
```
|
||||
*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
|
||||
|
||||
|
@ -193,6 +175,114 @@ 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 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
|
||||
|
||||
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:
|
||||
|
||||
```go
|
||||
name := gjson.Get(json, `programmers.#[lastName="Hunter"].firstName`)
|
||||
name := gjson.Get(json, `programmers.#(lastName="Hunter").firstName`)
|
||||
println(name.String()) // prints "Elliotte"
|
||||
```
|
||||
|
||||
|
|
|
@ -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
|
@ -1,4 +1,4 @@
|
|||
//+build appengine
|
||||
//+build appengine js
|
||||
|
||||
package gjson
|
||||
|
||||
|
@ -8,3 +8,11 @@ func getBytes(json []byte, path string) Result {
|
|||
func fillIndex(json string, c *parseContext) {
|
||||
// noop. Use zero for the Index value.
|
||||
}
|
||||
|
||||
func stringBytes(s string) []byte {
|
||||
return []byte(s)
|
||||
}
|
||||
|
||||
func bytesString(b []byte) string {
|
||||
return string(b)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
//+build !appengine
|
||||
//+build !js
|
||||
|
||||
package gjson
|
||||
|
||||
|
@ -15,45 +16,40 @@ func getBytes(json []byte, path string) Result {
|
|||
if json != nil {
|
||||
// unsafe cast to string
|
||||
result = Get(*(*string)(unsafe.Pointer(&json)), path)
|
||||
result = fromBytesGet(result)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func fromBytesGet(result Result) Result {
|
||||
// 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 {
|
||||
// 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 = ""
|
||||
} else {
|
||||
// raw has data, safely copy the slice header to a string
|
||||
// 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)))
|
||||
}
|
||||
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
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -11,6 +11,8 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/tidwall/pretty"
|
||||
)
|
||||
|
||||
// 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")
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
var s string
|
||||
json.Unmarshal([]byte(value.Raw), &s)
|
||||
|
@ -142,18 +146,21 @@ var basicJSON = `{"age":100, "name":{"here":"B\\\"R"},
|
|||
var basicJSONB = []byte(basicJSON)
|
||||
|
||||
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) {
|
||||
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}
|
||||
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 {
|
||||
|
@ -171,7 +178,8 @@ func TestManyVariousPathCounts(t *testing.T) {
|
|||
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())
|
||||
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) {
|
||||
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, Result{Type: JSON, Raw: "**invalid**"}.Value() == 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 {
|
||||
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())
|
||||
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())
|
||||
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())
|
||||
t.Fatalf("expected %v/%v got %v/%v", "email", "aaaa",
|
||||
key.String(), value.String())
|
||||
}
|
||||
}
|
||||
i++
|
||||
|
@ -395,7 +409,8 @@ func TestBasic2(t *testing.T) {
|
|||
if mtok.String() != "1002.3" {
|
||||
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" {
|
||||
t.Fatalf("expected %v, got %v", "Jason", mtok.String())
|
||||
}
|
||||
|
@ -403,6 +418,10 @@ func TestBasic2(t *testing.T) {
|
|||
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())
|
||||
|
@ -416,13 +435,16 @@ func TestBasic2(t *testing.T) {
|
|||
}
|
||||
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)
|
||||
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)
|
||||
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 {
|
||||
|
@ -462,7 +484,8 @@ func TestBasic4(t *testing.T) {
|
|||
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)
|
||||
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)
|
||||
|
@ -518,7 +541,8 @@ func TestBasic5(t *testing.T) {
|
|||
_ = 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())
|
||||
t.Fatal("expecting '"+`{"what is a wren?":"a bird"}`+"'", "got",
|
||||
token.String())
|
||||
}
|
||||
_ = token.Value().(map[string]interface{})
|
||||
|
||||
|
@ -528,8 +552,10 @@ func TestBasic5(t *testing.T) {
|
|||
|
||||
get(basicJSON, "vals.hello")
|
||||
|
||||
mm := Parse(basicJSON).Value().(map[string]interface{})
|
||||
fn := mm["loggy"].(map[string]interface{})["programmers"].([]interface{})[1].(map[string]interface{})["firstName"].(string)
|
||||
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)
|
||||
}
|
||||
|
@ -577,12 +603,18 @@ func TestLess(t *testing.T) {
|
|||
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, 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"))
|
||||
|
@ -764,7 +796,8 @@ func TestManyBasic(t *testing.T) {
|
|||
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(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")
|
||||
|
@ -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, 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
|
||||
for i := 0; i < 2; i++ {
|
||||
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++ {
|
||||
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) {
|
||||
json := `{ "name": "FirstName", "name1": "FirstName1", "address": "address1", "addressDetails": "address2", }`
|
||||
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) })
|
||||
t.Run("SingleMany", func(t *testing.T) {
|
||||
testMany(t, json, paths,
|
||||
expected)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIssue21(t *testing.T) {
|
||||
|
@ -817,9 +856,14 @@ func TestIssue21(t *testing.T) {
|
|||
"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) })
|
||||
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) {
|
||||
|
@ -965,7 +1009,8 @@ func TestUnmarshal(t *testing.T) {
|
|||
t.Fatal("not equal")
|
||||
}
|
||||
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)
|
||||
}
|
||||
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",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, `{"a":"b","a": 1, "c":{"hi":"there", "easy":["going",`+
|
||||
`{"mixed":"bag"}]} }`, true)
|
||||
testvalid(t, `""`, true)
|
||||
testvalid(t, `"`, false)
|
||||
testvalid(t, `"\n"`, true)
|
||||
|
@ -1049,7 +1095,8 @@ func TestValidBasic(t *testing.T) {
|
|||
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) {
|
||||
var bb []byte
|
||||
|
@ -1058,6 +1105,7 @@ func makeRandomJSONChars(b []byte) {
|
|||
}
|
||||
copy(b, bb[:len(b)])
|
||||
}
|
||||
|
||||
func TestValidRandom(t *testing.T) {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
b := make([]byte, 100000)
|
||||
|
@ -1077,7 +1125,8 @@ func TestValidRandom(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"}
|
||||
expected := []string{"[605]", "99", "my mybar", ""}
|
||||
results := GetMany(json, paths...)
|
||||
|
@ -1086,7 +1135,8 @@ func TestGetMany47(t *testing.T) {
|
|||
}
|
||||
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)
|
||||
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 {
|
||||
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) {
|
||||
start := time.Now()
|
||||
for time.Since(start) < time.Second*3 {
|
||||
testRandomGetMany(t)
|
||||
}
|
||||
}
|
||||
// func TestRandomGetMany(t *testing.T) {
|
||||
// start := time.Now()
|
||||
// for time.Since(start) < time.Second*3 {
|
||||
// testRandomGetMany(t)
|
||||
// }
|
||||
// }
|
||||
func testRandomGetMany(t *testing.T) {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
json, keys := randomJSON()
|
||||
|
@ -1183,15 +1234,18 @@ func TestIssue54(t *testing.T) {
|
|||
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))
|
||||
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))
|
||||
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))
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
var json string
|
||||
if array {
|
||||
|
@ -1357,7 +1412,7 @@ null
|
|||
}
|
||||
|
||||
func TestNumUint64String(t *testing.T) {
|
||||
i := 9007199254740993 //2^53 + 1
|
||||
var i int64 = 9007199254740993 //2^53 + 1
|
||||
j := fmt.Sprintf(`{"data": [ %d, "hello" ] }`, i)
|
||||
res := Get(j, "data.0")
|
||||
if res.String() != "9007199254740993" {
|
||||
|
@ -1366,7 +1421,7 @@ func TestNumUint64String(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestNumInt64String(t *testing.T) {
|
||||
i := -9007199254740993
|
||||
var i int64 = -9007199254740993
|
||||
j := fmt.Sprintf(`{"data":[ "hello", %d ]}`, i)
|
||||
res := Get(j, "data.1")
|
||||
if res.String() != "-9007199254740993" {
|
||||
|
@ -1379,12 +1434,13 @@ func TestNumBigString(t *testing.T) {
|
|||
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())
|
||||
t.Fatalf("expected '%v', got '%v'", "900719925474099301239109123101",
|
||||
res.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestNumFloatString(t *testing.T) {
|
||||
i := -9007199254740993
|
||||
var i int64 = -9007199254740993
|
||||
j := fmt.Sprintf(`{"data":[ "hello", %d ]}`, i) //No quotes around value!!
|
||||
res := Get(j, "data.1")
|
||||
if res.String() != "-9007199254740993" {
|
||||
|
@ -1418,8 +1474,10 @@ func TestArrayValues(t *testing.T) {
|
|||
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: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 {
|
||||
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
|
@ -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=
|
Loading…
Reference in New Issue