Update gjson/sjson
This commit is contained in:
parent
cc75cf22a8
commit
3e41a2ecce
12
Gopkg.lock
generated
12
Gopkg.lock
generated
@ -238,12 +238,12 @@
|
|||||||
revision = "5302514a34feb71743bf597938742b51831ba289"
|
revision = "5302514a34feb71743bf597938742b51831ba289"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:211773b67c5594aa92b1e8389c59558fa4927614507ea38237265e00c0ba6b81"
|
digest = "1:3ddca2bd5496c6922a2a9e636530e178a43c2a534ea6634211acdc7d10222794"
|
||||||
name = "github.com/tidwall/gjson"
|
name = "github.com/tidwall/gjson"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
pruneopts = ""
|
pruneopts = ""
|
||||||
revision = "5a69e67cfd8f6f9b0044ed49f5079d0eeed28653"
|
revision = "1e3f6aeaa5bad08d777ea7807b279a07885dd8b2"
|
||||||
version = "v1.0.1"
|
version = "v1.1.3"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
@ -310,12 +310,12 @@
|
|||||||
revision = "d4a8a3d30d5729f85edfba1745241f3a621d0359"
|
revision = "d4a8a3d30d5729f85edfba1745241f3a621d0359"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:ed6a1c415a0bd35c9c18eec74bfd460a57ba21fb3bc0da629afc275096edffa4"
|
digest = "1:ca969d3e75ed5b3003f4f5864bb5c13d99471ef57f9049bf78562d7ee1ac019c"
|
||||||
name = "github.com/tidwall/sjson"
|
name = "github.com/tidwall/sjson"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
pruneopts = ""
|
pruneopts = ""
|
||||||
revision = "6a22caf2fd45d5e2119bfc3717e984f15a7eb7ee"
|
revision = "48d34adceb39a5bd6ed7c12f38c78cd425436442"
|
||||||
version = "v1.0.0"
|
version = "v1.0.2"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
124
vendor/github.com/tidwall/gjson/README.md
generated
vendored
124
vendor/github.com/tidwall/gjson/README.md
generated
vendored
@ -5,15 +5,17 @@
|
|||||||
<br>
|
<br>
|
||||||
<a href="https://travis-ci.org/tidwall/gjson"><img src="https://img.shields.io/travis/tidwall/gjson.svg?style=flat-square" alt="Build Status"></a>
|
<a href="https://travis-ci.org/tidwall/gjson"><img src="https://img.shields.io/travis/tidwall/gjson.svg?style=flat-square" alt="Build Status"></a>
|
||||||
<a href="https://godoc.org/github.com/tidwall/gjson"><img src="https://img.shields.io/badge/api-reference-blue.svg?style=flat-square" alt="GoDoc"></a>
|
<a href="https://godoc.org/github.com/tidwall/gjson"><img src="https://img.shields.io/badge/api-reference-blue.svg?style=flat-square" alt="GoDoc"></a>
|
||||||
<a href="http://tidwall.com/gjson-play"><img src="https://img.shields.io/badge/play-ground-orange.svg?style=flat-square" alt="GJSON Playground"></a>
|
<a href="http://tidwall.com/gjson-play"><img src="https://img.shields.io/badge/%F0%9F%8F%90-playground-9900cc.svg?style=flat-square" alt="GJSON Playground"></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<p align="center">get a json value quickly</a></p>
|
<p align="center">get json values quickly</a></p>
|
||||||
|
|
||||||
GJSON is a Go package that provides a [fast](#performance) and [simple](#get-a-value) way to get values from a json document.
|
GJSON is a Go package that provides a [fast](#performance) and [simple](#get-a-value) way to get values from a json document.
|
||||||
It has features such as [one line retrieval](#get-a-value), [dot notation paths](#path-syntax), [iteration](#iterate-through-an-object-or-array).
|
It has features such as [one line retrieval](#get-a-value), [dot notation paths](#path-syntax), [iteration](#iterate-through-an-object-or-array), and [parsing json lines](#json-lines).
|
||||||
|
|
||||||
|
Also check out [SJSON](https://github.com/tidwall/sjson) for modifying json, and the [JJ](https://github.com/tidwall/jj) command line tool.
|
||||||
|
|
||||||
Getting Started
|
Getting Started
|
||||||
===============
|
===============
|
||||||
@ -29,7 +31,7 @@ $ go get -u github.com/tidwall/gjson
|
|||||||
This will retrieve the library.
|
This will retrieve the library.
|
||||||
|
|
||||||
## Get a value
|
## Get a value
|
||||||
Get searches json for the specified path. A path is in dot syntax, such as "name.last" or "age". This function expects that the json is well-formed. Bad json will not panic, but it may return back unexpected results. When the value is found it's returned immediately.
|
Get searches json for the specified path. A path is in dot syntax, such as "name.last" or "age". When the value is found it's returned immediately.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
package main
|
package main
|
||||||
@ -95,6 +97,36 @@ friends.#[age>45]#.last >> ["Craig","Murphy"]
|
|||||||
friends.#[first%"D*"].last >> "Murphy"
|
friends.#[first%"D*"].last >> "Murphy"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 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
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
## Result Type
|
## Result Type
|
||||||
|
|
||||||
GJSON supports the json types `string`, `number`, `bool`, and `null`.
|
GJSON supports the json types `string`, `number`, `bool`, and `null`.
|
||||||
@ -152,6 +184,15 @@ array >> []interface{}
|
|||||||
object >> map[string]interface{}
|
object >> map[string]interface{}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 64-bit integers
|
||||||
|
|
||||||
|
The `result.Int()` and `result.Uint()` calls are capable of reading all 64 bits, allowing for large JSON integers.
|
||||||
|
|
||||||
|
```go
|
||||||
|
result.Int() int64 // -9223372036854775808 to 9223372036854775807
|
||||||
|
result.Uint() int64 // 0 to 18446744073709551615
|
||||||
|
```
|
||||||
|
|
||||||
## 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:
|
||||||
@ -234,51 +275,17 @@ if gjson.Get(json, "name.last").Exists() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Unmarshalling
|
## Validate JSON
|
||||||
|
|
||||||
There's a `gjson.Unmarshal` function which loads json data into a value.
|
The `Get*` and `Parse*` functions expects that the json is well-formed. Bad json will not panic, but it may return back unexpected results.
|
||||||
It's a general replacement for `json.Unmarshal` and you can typically
|
|
||||||
see a 2-3x boost in performance without the need for external generators.
|
|
||||||
|
|
||||||
This function works almost identically to `json.Unmarshal` except that
|
|
||||||
`gjson.Unmarshal` will automatically attempt to convert JSON values to any
|
|
||||||
Go type. For example, the JSON string "100" or the JSON number 100 can be
|
|
||||||
equally assigned to Go string, int, byte, uint64, etc. This rule applies to
|
|
||||||
all types.
|
|
||||||
|
|
||||||
|
If you are consuming JSON from an unpredictable source then you may want to validate prior to using GJSON.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
package main
|
if !gjson.Valid(json) {
|
||||||
|
return errors.New("invalid json")
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/tidwall/gjson"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Animal struct {
|
|
||||||
Type string `json:"type"`
|
|
||||||
Sound string `json:"sound"`
|
|
||||||
Age int `json:"age"`
|
|
||||||
}
|
}
|
||||||
|
value := gjson.Get(json, "name.last")
|
||||||
var json = `{
|
|
||||||
"type": "Dog",
|
|
||||||
"Sound": "Bark",
|
|
||||||
"Age": "11"
|
|
||||||
}`
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
var dog Animal
|
|
||||||
gjson.Unmarshal([]byte(json), &dog)
|
|
||||||
fmt.Printf("type: %s, sound: %s, age: %d\n", dog.Type, dog.Sound, dog.Age)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This will print:
|
|
||||||
|
|
||||||
```
|
|
||||||
type: Dog, sound: Bark, age: 11
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Unmarshal to a map
|
## Unmarshal to a map
|
||||||
@ -318,7 +325,7 @@ This is a best-effort no allocation sub slice of the original json. This method
|
|||||||
|
|
||||||
## Get multiple values at once
|
## Get multiple values at once
|
||||||
|
|
||||||
The `GetMany` function can be used to get multiple values at the same time, and is optimized to scan over a JSON payload once.
|
The `GetMany` function can be used to get multiple values at the same time.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
results := gjson.GetMany(json, "name.first", "name.last", "age")
|
results := gjson.GetMany(json, "name.first", "name.last", "age")
|
||||||
@ -338,7 +345,6 @@ and [json-iterator](https://github.com/json-iterator/go)
|
|||||||
BenchmarkGJSONGet-8 3000000 372 ns/op 0 B/op 0 allocs/op
|
BenchmarkGJSONGet-8 3000000 372 ns/op 0 B/op 0 allocs/op
|
||||||
BenchmarkGJSONUnmarshalMap-8 900000 4154 ns/op 1920 B/op 26 allocs/op
|
BenchmarkGJSONUnmarshalMap-8 900000 4154 ns/op 1920 B/op 26 allocs/op
|
||||||
BenchmarkJSONUnmarshalMap-8 600000 9019 ns/op 3048 B/op 69 allocs/op
|
BenchmarkJSONUnmarshalMap-8 600000 9019 ns/op 3048 B/op 69 allocs/op
|
||||||
BenchmarkJSONUnmarshalStruct-8 600000 9268 ns/op 1832 B/op 69 allocs/op
|
|
||||||
BenchmarkJSONDecoder-8 300000 14120 ns/op 4224 B/op 184 allocs/op
|
BenchmarkJSONDecoder-8 300000 14120 ns/op 4224 B/op 184 allocs/op
|
||||||
BenchmarkFFJSONLexer-8 1500000 3111 ns/op 896 B/op 8 allocs/op
|
BenchmarkFFJSONLexer-8 1500000 3111 ns/op 896 B/op 8 allocs/op
|
||||||
BenchmarkEasyJSONLexer-8 3000000 887 ns/op 613 B/op 6 allocs/op
|
BenchmarkEasyJSONLexer-8 3000000 887 ns/op 613 B/op 6 allocs/op
|
||||||
@ -346,17 +352,6 @@ BenchmarkJSONParserGet-8 3000000 499 ns/op 21 B/op
|
|||||||
BenchmarkJSONIterator-8 3000000 812 ns/op 544 B/op 9 allocs/op
|
BenchmarkJSONIterator-8 3000000 812 ns/op 544 B/op 9 allocs/op
|
||||||
```
|
```
|
||||||
|
|
||||||
Benchmarks for the `GetMany` function:
|
|
||||||
|
|
||||||
```
|
|
||||||
BenchmarkGJSONGetMany4Paths-8 4000000 303 ns/op 112 B/op 0 allocs/op
|
|
||||||
BenchmarkGJSONGetMany8Paths-8 8000000 208 ns/op 56 B/op 0 allocs/op
|
|
||||||
BenchmarkGJSONGetMany16Paths-8 16000000 156 ns/op 56 B/op 0 allocs/op
|
|
||||||
BenchmarkGJSONGetMany32Paths-8 32000000 127 ns/op 64 B/op 0 allocs/op
|
|
||||||
BenchmarkGJSONGetMany64Paths-8 64000000 117 ns/op 64 B/op 0 allocs/op
|
|
||||||
BenchmarkGJSONGetMany128Paths-8 128000000 109 ns/op 64 B/op 0 allocs/op
|
|
||||||
```
|
|
||||||
|
|
||||||
JSON document used:
|
JSON document used:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@ -395,21 +390,6 @@ widget.image.hOffset
|
|||||||
widget.text.onMouseUp
|
widget.text.onMouseUp
|
||||||
```
|
```
|
||||||
|
|
||||||
For the `GetMany` benchmarks these paths are used:
|
|
||||||
|
|
||||||
```
|
|
||||||
widget.window.name
|
|
||||||
widget.image.hOffset
|
|
||||||
widget.text.onMouseUp
|
|
||||||
widget.window.title
|
|
||||||
widget.image.alignment
|
|
||||||
widget.text.style
|
|
||||||
widget.window.height
|
|
||||||
widget.image.src
|
|
||||||
widget.text.data
|
|
||||||
widget.text.size
|
|
||||||
```
|
|
||||||
|
|
||||||
*These benchmarks were run on a MacBook Pro 15" 2.8 GHz Intel Core i7 using Go 1.8 and can be be found [here](https://github.com/tidwall/gjson-benchmarks).*
|
*These benchmarks were run on a MacBook Pro 15" 2.8 GHz Intel Core i7 using Go 1.8 and can be be found [here](https://github.com/tidwall/gjson-benchmarks).*
|
||||||
|
|
||||||
|
|
||||||
|
621
vendor/github.com/tidwall/gjson/gjson.go
generated
vendored
621
vendor/github.com/tidwall/gjson/gjson.go
generated
vendored
@ -13,7 +13,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
"unicode/utf16"
|
"unicode/utf16"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/tidwall/match"
|
"github.com/tidwall/match"
|
||||||
)
|
)
|
||||||
@ -78,7 +77,20 @@ func (t Result) String() string {
|
|||||||
case False:
|
case False:
|
||||||
return "false"
|
return "false"
|
||||||
case Number:
|
case Number:
|
||||||
return strconv.FormatFloat(t.Num, 'f', -1, 64)
|
if len(t.Raw) == 0 {
|
||||||
|
// calculated result
|
||||||
|
return strconv.FormatFloat(t.Num, 'f', -1, 64)
|
||||||
|
}
|
||||||
|
var i int
|
||||||
|
if t.Raw[0] == '-' {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
for ; i < len(t.Raw); i++ {
|
||||||
|
if t.Raw[i] < '0' || t.Raw[i] > '9' {
|
||||||
|
return strconv.FormatFloat(t.Num, 'f', -1, 64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return t.Raw
|
||||||
case String:
|
case String:
|
||||||
return t.Str
|
return t.Str
|
||||||
case JSON:
|
case JSON:
|
||||||
@ -96,7 +108,7 @@ func (t Result) Bool() bool {
|
|||||||
case True:
|
case True:
|
||||||
return true
|
return true
|
||||||
case String:
|
case String:
|
||||||
return t.Str != "" && t.Str != "0"
|
return t.Str != "" && t.Str != "0" && t.Str != "false"
|
||||||
case Number:
|
case Number:
|
||||||
return t.Num != 0
|
return t.Num != 0
|
||||||
}
|
}
|
||||||
@ -177,8 +189,8 @@ func (t Result) Time() time.Time {
|
|||||||
// If the result represents a non-existent value, then an empty array will be returned.
|
// If the result represents a non-existent value, then an empty array will be returned.
|
||||||
// If the result is not a JSON array, the return value will be an array containing one result.
|
// If the result is not a JSON array, the return value will be an array containing one result.
|
||||||
func (t Result) Array() []Result {
|
func (t Result) Array() []Result {
|
||||||
if !t.Exists() {
|
if t.Type == Null {
|
||||||
return nil
|
return []Result{}
|
||||||
}
|
}
|
||||||
if t.Type != JSON {
|
if t.Type != JSON {
|
||||||
return []Result{t}
|
return []Result{t}
|
||||||
@ -192,7 +204,7 @@ func (t Result) IsObject() bool {
|
|||||||
return t.Type == JSON && len(t.Raw) > 0 && t.Raw[0] == '{'
|
return t.Type == JSON && len(t.Raw) > 0 && t.Raw[0] == '{'
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsObject returns true if the result value is a JSON array.
|
// IsArray returns true if the result value is a JSON array.
|
||||||
func (t Result) IsArray() bool {
|
func (t Result) IsArray() bool {
|
||||||
return t.Type == JSON && len(t.Raw) > 0 && t.Raw[0] == '['
|
return t.Type == JSON && len(t.Raw) > 0 && t.Raw[0] == '['
|
||||||
}
|
}
|
||||||
@ -345,24 +357,30 @@ func (t Result) arrayOrMap(vc byte, valueize bool) (r arrayOrMapResult) {
|
|||||||
if (json[i] >= '0' && json[i] <= '9') || json[i] == '-' {
|
if (json[i] >= '0' && json[i] <= '9') || json[i] == '-' {
|
||||||
value.Type = Number
|
value.Type = Number
|
||||||
value.Raw, value.Num = tonum(json[i:])
|
value.Raw, value.Num = tonum(json[i:])
|
||||||
|
value.Str = ""
|
||||||
} else {
|
} else {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
case '{', '[':
|
case '{', '[':
|
||||||
value.Type = JSON
|
value.Type = JSON
|
||||||
value.Raw = squash(json[i:])
|
value.Raw = squash(json[i:])
|
||||||
|
value.Str, value.Num = "", 0
|
||||||
case 'n':
|
case 'n':
|
||||||
value.Type = Null
|
value.Type = Null
|
||||||
value.Raw = tolit(json[i:])
|
value.Raw = tolit(json[i:])
|
||||||
|
value.Str, value.Num = "", 0
|
||||||
case 't':
|
case 't':
|
||||||
value.Type = True
|
value.Type = True
|
||||||
value.Raw = tolit(json[i:])
|
value.Raw = tolit(json[i:])
|
||||||
|
value.Str, value.Num = "", 0
|
||||||
case 'f':
|
case 'f':
|
||||||
value.Type = False
|
value.Type = False
|
||||||
value.Raw = tolit(json[i:])
|
value.Raw = tolit(json[i:])
|
||||||
|
value.Str, value.Num = "", 0
|
||||||
case '"':
|
case '"':
|
||||||
value.Type = String
|
value.Type = String
|
||||||
value.Raw, value.Str = tostr(json[i:])
|
value.Raw, value.Str = tostr(json[i:])
|
||||||
|
value.Num = 0
|
||||||
}
|
}
|
||||||
i += len(value.Raw) - 1
|
i += len(value.Raw) - 1
|
||||||
|
|
||||||
@ -371,9 +389,13 @@ func (t Result) arrayOrMap(vc byte, valueize bool) (r arrayOrMapResult) {
|
|||||||
key = value
|
key = value
|
||||||
} else {
|
} else {
|
||||||
if valueize {
|
if valueize {
|
||||||
r.oi[key.Str] = value.Value()
|
if _, ok := r.oi[key.Str]; !ok {
|
||||||
|
r.oi[key.Str] = value.Value()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
r.o[key.Str] = value
|
if _, ok := r.o[key.Str]; !ok {
|
||||||
|
r.o[key.Str] = value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
count++
|
count++
|
||||||
@ -390,6 +412,11 @@ end:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse parses the json and returns a result.
|
// Parse parses the json and returns a result.
|
||||||
|
//
|
||||||
|
// This function expects that the json is well-formed, and does not validate.
|
||||||
|
// Invalid json will not panic, but it may return back unexpected results.
|
||||||
|
// If you are consuming JSON from an unpredictable source then you may want to
|
||||||
|
// use the Valid function first.
|
||||||
func Parse(json string) Result {
|
func Parse(json string) Result {
|
||||||
var value Result
|
var value Result
|
||||||
for i := 0; i < len(json); i++ {
|
for i := 0; i < len(json); i++ {
|
||||||
@ -511,7 +538,7 @@ func tonum(json string) (raw string, num float64) {
|
|||||||
|
|
||||||
func tolit(json string) (raw string) {
|
func tolit(json string) (raw string) {
|
||||||
for i := 1; i < len(json); i++ {
|
for i := 1; i < len(json); i++ {
|
||||||
if json[i] <= 'a' || json[i] >= 'z' {
|
if json[i] < 'a' || json[i] > 'z' {
|
||||||
return json[:i]
|
return json[:i]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -578,6 +605,8 @@ func (t Result) Exists() bool {
|
|||||||
// Number, for JSON numbers
|
// Number, for JSON numbers
|
||||||
// string, for JSON string literals
|
// string, for JSON string literals
|
||||||
// nil, for JSON null
|
// nil, for JSON null
|
||||||
|
// map[string]interface{}, for JSON objects
|
||||||
|
// []interface{}, for JSON arrays
|
||||||
//
|
//
|
||||||
func (t Result) Value() interface{} {
|
func (t Result) Value() interface{} {
|
||||||
if t.Type == String {
|
if t.Type == String {
|
||||||
@ -1077,7 +1106,7 @@ func queryMatches(rp *arrayPathResult, value Result) bool {
|
|||||||
case "=":
|
case "=":
|
||||||
return value.Num == rpvn
|
return value.Num == rpvn
|
||||||
case "!=":
|
case "!=":
|
||||||
return value.Num == rpvn
|
return value.Num != rpvn
|
||||||
case "<":
|
case "<":
|
||||||
return value.Num < rpvn
|
return value.Num < rpvn
|
||||||
case "<=":
|
case "<=":
|
||||||
@ -1128,7 +1157,7 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
|
|||||||
partidx = int(n)
|
partidx = int(n)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i < len(c.json) {
|
for i < len(c.json)+1 {
|
||||||
if !rp.arrch {
|
if !rp.arrch {
|
||||||
pmatch = partidx == h
|
pmatch = partidx == h
|
||||||
hit = pmatch && !rp.more
|
hit = pmatch && !rp.more
|
||||||
@ -1137,8 +1166,16 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
|
|||||||
if rp.alogok {
|
if rp.alogok {
|
||||||
alog = append(alog, i)
|
alog = append(alog, i)
|
||||||
}
|
}
|
||||||
for ; i < len(c.json); i++ {
|
for ; ; i++ {
|
||||||
switch c.json[i] {
|
var ch byte
|
||||||
|
if i > len(c.json) {
|
||||||
|
break
|
||||||
|
} else if i == len(c.json) {
|
||||||
|
ch = ']'
|
||||||
|
} else {
|
||||||
|
ch = c.json[i]
|
||||||
|
}
|
||||||
|
switch ch {
|
||||||
default:
|
default:
|
||||||
continue
|
continue
|
||||||
case '"':
|
case '"':
|
||||||
@ -1252,14 +1289,18 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
|
|||||||
if rp.alogok {
|
if rp.alogok {
|
||||||
var jsons = make([]byte, 0, 64)
|
var jsons = make([]byte, 0, 64)
|
||||||
jsons = append(jsons, '[')
|
jsons = append(jsons, '[')
|
||||||
|
|
||||||
for j, k := 0, 0; j < len(alog); j++ {
|
for j, k := 0, 0; j < len(alog); j++ {
|
||||||
res := Get(c.json[alog[j]:], rp.alogkey)
|
_, res, ok := parseAny(c.json, alog[j], true)
|
||||||
if res.Exists() {
|
if ok {
|
||||||
if k > 0 {
|
res := res.Get(rp.alogkey)
|
||||||
jsons = append(jsons, ',')
|
if res.Exists() {
|
||||||
|
if k > 0 {
|
||||||
|
jsons = append(jsons, ',')
|
||||||
|
}
|
||||||
|
jsons = append(jsons, []byte(res.Raw)...)
|
||||||
|
k++
|
||||||
}
|
}
|
||||||
jsons = append(jsons, []byte(res.Raw)...)
|
|
||||||
k++
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jsons = append(jsons, ']')
|
jsons = append(jsons, ']')
|
||||||
@ -1270,7 +1311,7 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
|
|||||||
if rp.alogok {
|
if rp.alogok {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
c.value.Raw = val
|
c.value.Raw = ""
|
||||||
c.value.Type = Number
|
c.value.Type = Number
|
||||||
c.value.Num = float64(h - 1)
|
c.value.Num = float64(h - 1)
|
||||||
c.calcd = true
|
c.calcd = true
|
||||||
@ -1290,16 +1331,32 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
|
|||||||
return i, false
|
return i, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ForEachLine iterates through lines of JSON as specified by the JSON Lines
|
||||||
|
// format (http://jsonlines.org/).
|
||||||
|
// Each line is returned as a GJSON Result.
|
||||||
|
func ForEachLine(json string, iterator func(line Result) bool) {
|
||||||
|
var res Result
|
||||||
|
var i int
|
||||||
|
for {
|
||||||
|
i, res, _ = parseAny(json, i, true)
|
||||||
|
if !res.Exists() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !iterator(res) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type parseContext struct {
|
type parseContext struct {
|
||||||
json string
|
json string
|
||||||
value Result
|
value Result
|
||||||
calcd bool
|
calcd bool
|
||||||
|
lines bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get searches json for the specified path.
|
// Get searches json for the specified path.
|
||||||
// A path is in dot syntax, such as "name.last" or "age".
|
// A path is in dot syntax, such as "name.last" or "age".
|
||||||
// This function expects that the json is well-formed, and does not validate.
|
|
||||||
// Invalid json will not panic, but it may return back unexpected results.
|
|
||||||
// When the value is found it's returned immediately.
|
// When the value is found it's returned immediately.
|
||||||
//
|
//
|
||||||
// A path is a series of keys searated by a dot.
|
// A path is a series of keys searated by a dot.
|
||||||
@ -1326,79 +1383,38 @@ type parseContext struct {
|
|||||||
// "c?ildren.0" >> "Sara"
|
// "c?ildren.0" >> "Sara"
|
||||||
// "friends.#.first" >> ["James","Roger"]
|
// "friends.#.first" >> ["James","Roger"]
|
||||||
//
|
//
|
||||||
|
// This function expects that the json is well-formed, and does not validate.
|
||||||
|
// Invalid json will not panic, but it may return back unexpected results.
|
||||||
|
// If you are consuming JSON from an unpredictable source then you may want to
|
||||||
|
// use the Valid function first.
|
||||||
func Get(json, path string) Result {
|
func Get(json, path string) Result {
|
||||||
var i int
|
var i int
|
||||||
var c = &parseContext{json: json}
|
var c = &parseContext{json: json}
|
||||||
for ; i < len(c.json); i++ {
|
if len(path) >= 2 && path[0] == '.' && path[1] == '.' {
|
||||||
if c.json[i] == '{' {
|
c.lines = true
|
||||||
i++
|
parseArray(c, 0, path[2:])
|
||||||
parseObject(c, i, path)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if c.json[i] == '[' {
|
|
||||||
i++
|
|
||||||
parseArray(c, i, path)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(c.value.Raw) > 0 && !c.calcd {
|
|
||||||
jhdr := *(*reflect.StringHeader)(unsafe.Pointer(&json))
|
|
||||||
rhdr := *(*reflect.StringHeader)(unsafe.Pointer(&(c.value.Raw)))
|
|
||||||
c.value.Index = int(rhdr.Data - jhdr.Data)
|
|
||||||
if c.value.Index < 0 || c.value.Index >= len(json) {
|
|
||||||
c.value.Index = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return c.value
|
|
||||||
}
|
|
||||||
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 {
|
|
||||||
// raw is nil
|
|
||||||
result.Raw = ""
|
|
||||||
} else {
|
|
||||||
// raw has data, safely copy the slice header to a string
|
|
||||||
result.Raw = string(*(*[]byte)(unsafe.Pointer(&rawh)))
|
|
||||||
}
|
|
||||||
result.Str = ""
|
|
||||||
} else if rawh.Data == 0 {
|
|
||||||
// raw is nil
|
|
||||||
result.Raw = ""
|
|
||||||
// str has data, safely copy the slice header to a string
|
|
||||||
result.Str = string(*(*[]byte)(unsafe.Pointer(&strh)))
|
|
||||||
} else if strh.Data >= rawh.Data &&
|
|
||||||
int(strh.Data)+strh.Len <= int(rawh.Data)+rawh.Len {
|
|
||||||
// Str is a substring of Raw.
|
|
||||||
start := int(strh.Data - rawh.Data)
|
|
||||||
// safely copy the raw slice header
|
|
||||||
result.Raw = string(*(*[]byte)(unsafe.Pointer(&rawh)))
|
|
||||||
// substring the raw
|
|
||||||
result.Str = result.Raw[start : start+strh.Len]
|
|
||||||
} else {
|
} else {
|
||||||
// safely copy both the raw and str slice headers to strings
|
for ; i < len(c.json); i++ {
|
||||||
result.Raw = string(*(*[]byte)(unsafe.Pointer(&rawh)))
|
if c.json[i] == '{' {
|
||||||
result.Str = string(*(*[]byte)(unsafe.Pointer(&strh)))
|
i++
|
||||||
|
parseObject(c, i, path)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if c.json[i] == '[' {
|
||||||
|
i++
|
||||||
|
parseArray(c, i, path)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result
|
fillIndex(json, c)
|
||||||
|
return c.value
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBytes searches json for the specified path.
|
// GetBytes searches json for the specified path.
|
||||||
// If working with bytes, this method preferred over Get(string(data), path)
|
// If working with bytes, this method preferred over Get(string(data), path)
|
||||||
func GetBytes(json []byte, path string) Result {
|
func GetBytes(json []byte, path string) Result {
|
||||||
var result Result
|
return getBytes(json, path)
|
||||||
if json != nil {
|
|
||||||
// unsafe cast to string
|
|
||||||
result = Get(*(*string)(unsafe.Pointer(&json)), path)
|
|
||||||
result = fromBytesGet(result)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// runeit returns the rune from the the \uXXXX
|
// runeit returns the rune from the the \uXXXX
|
||||||
@ -1595,405 +1611,22 @@ var ( // used for testing
|
|||||||
testLastWasFallback bool
|
testLastWasFallback bool
|
||||||
)
|
)
|
||||||
|
|
||||||
// areSimplePaths returns true if all the paths are simple enough
|
|
||||||
// to parse quickly for GetMany(). Allows alpha-numeric, dots,
|
|
||||||
// underscores, and the dollar sign. It does not allow non-alnum,
|
|
||||||
// escape characters, or keys which start with a numbers.
|
|
||||||
// For example:
|
|
||||||
// "name.last" == OK
|
|
||||||
// "user.id0" == OK
|
|
||||||
// "user.ID" == OK
|
|
||||||
// "user.first_name" == OK
|
|
||||||
// "user.firstName" == OK
|
|
||||||
// "user.0item" == BAD
|
|
||||||
// "user.#id" == BAD
|
|
||||||
// "user\.name" == BAD
|
|
||||||
func areSimplePaths(paths []string) bool {
|
|
||||||
for _, path := range paths {
|
|
||||||
var fi int // first key index, for keys with numeric prefix
|
|
||||||
for i := 0; i < len(path); i++ {
|
|
||||||
if path[i] >= 'a' && path[i] <= 'z' {
|
|
||||||
// a-z is likely to be the highest frequency charater.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if path[i] == '.' {
|
|
||||||
fi = i + 1
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if path[i] >= 'A' && path[i] <= 'Z' {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if path[i] == '_' || path[i] == '$' {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if i > fi && path[i] >= '0' && path[i] <= '9' {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMany searches json for the multiple paths.
|
// GetMany searches json for the multiple paths.
|
||||||
// The return value is a Result array where the number of items
|
// The return value is a Result array where the number of items
|
||||||
// will be equal to the number of input paths.
|
// will be equal to the number of input paths.
|
||||||
func GetMany(json string, paths ...string) []Result {
|
func GetMany(json string, path ...string) []Result {
|
||||||
if len(paths) < 4 {
|
res := make([]Result, len(path))
|
||||||
if testWatchForFallback {
|
for i, path := range path {
|
||||||
testLastWasFallback = false
|
res[i] = Get(json, path)
|
||||||
}
|
|
||||||
switch len(paths) {
|
|
||||||
case 0:
|
|
||||||
// return nil when no paths are specified.
|
|
||||||
return nil
|
|
||||||
case 1:
|
|
||||||
return []Result{Get(json, paths[0])}
|
|
||||||
case 2:
|
|
||||||
return []Result{Get(json, paths[0]), Get(json, paths[1])}
|
|
||||||
case 3:
|
|
||||||
return []Result{Get(json, paths[0]), Get(json, paths[1]), Get(json, paths[2])}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
var results []Result
|
return res
|
||||||
var ok bool
|
|
||||||
var i int
|
|
||||||
if len(paths) > 512 {
|
|
||||||
// we can only support up to 512 paths. Is that too many?
|
|
||||||
goto fallback
|
|
||||||
}
|
|
||||||
if !areSimplePaths(paths) {
|
|
||||||
// If there is even one path that is not considered "simple" then
|
|
||||||
// we need to use the fallback method.
|
|
||||||
goto fallback
|
|
||||||
}
|
|
||||||
// locate the object token.
|
|
||||||
for ; i < len(json); i++ {
|
|
||||||
if json[i] == '{' {
|
|
||||||
i++
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if json[i] <= ' ' {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
goto fallback
|
|
||||||
}
|
|
||||||
// use the call function table.
|
|
||||||
if len(paths) <= 8 {
|
|
||||||
results, ok = getMany8(json, i, paths)
|
|
||||||
} else if len(paths) <= 16 {
|
|
||||||
results, ok = getMany16(json, i, paths)
|
|
||||||
} else if len(paths) <= 32 {
|
|
||||||
results, ok = getMany32(json, i, paths)
|
|
||||||
} else if len(paths) <= 64 {
|
|
||||||
results, ok = getMany64(json, i, paths)
|
|
||||||
} else if len(paths) <= 128 {
|
|
||||||
results, ok = getMany128(json, i, paths)
|
|
||||||
} else if len(paths) <= 256 {
|
|
||||||
results, ok = getMany256(json, i, paths)
|
|
||||||
} else if len(paths) <= 512 {
|
|
||||||
results, ok = getMany512(json, i, paths)
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
// there was some fault while parsing. we should try the
|
|
||||||
// fallback method. This could result in performance
|
|
||||||
// degregation in some cases.
|
|
||||||
goto fallback
|
|
||||||
}
|
|
||||||
if testWatchForFallback {
|
|
||||||
testLastWasFallback = false
|
|
||||||
}
|
|
||||||
return results
|
|
||||||
fallback:
|
|
||||||
results = results[:0]
|
|
||||||
for i := 0; i < len(paths); i++ {
|
|
||||||
results = append(results, Get(json, paths[i]))
|
|
||||||
}
|
|
||||||
if testWatchForFallback {
|
|
||||||
testLastWasFallback = true
|
|
||||||
}
|
|
||||||
return results
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetManyBytes searches json for the specified path.
|
// GetManyBytes searches json for the multiple paths.
|
||||||
// If working with bytes, this method preferred over
|
// The return value is a Result array where the number of items
|
||||||
// GetMany(string(data), paths...)
|
// will be equal to the number of input paths.
|
||||||
func GetManyBytes(json []byte, paths ...string) []Result {
|
func GetManyBytes(json []byte, path ...string) []Result {
|
||||||
if json == nil {
|
return GetMany(string(json), path...)
|
||||||
return GetMany("", paths...)
|
|
||||||
}
|
|
||||||
results := GetMany(*(*string)(unsafe.Pointer(&json)), paths...)
|
|
||||||
for i := range results {
|
|
||||||
results[i] = fromBytesGet(results[i])
|
|
||||||
}
|
|
||||||
return results
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseGetMany parses a json object for keys that match against the callers
|
|
||||||
// paths. It's a best-effort attempt and quickly locating and assigning the
|
|
||||||
// values to the []Result array. If there are failures such as bad json, or
|
|
||||||
// invalid input paths, or too much recursion, the function will exit with a
|
|
||||||
// return value of 'false'.
|
|
||||||
func parseGetMany(
|
|
||||||
json string, i int,
|
|
||||||
level uint, kplen int,
|
|
||||||
paths []string, completed []bool, matches []uint64, results []Result,
|
|
||||||
) (int, bool) {
|
|
||||||
if level > 62 {
|
|
||||||
// The recursion level is limited because the matches []uint64
|
|
||||||
// array cannot handle more the 64-bits.
|
|
||||||
return i, false
|
|
||||||
}
|
|
||||||
// At this point the last character read was a '{'.
|
|
||||||
// Read all object keys and try to match against the paths.
|
|
||||||
var key string
|
|
||||||
var val string
|
|
||||||
var vesc, ok bool
|
|
||||||
next_key:
|
|
||||||
for ; i < len(json); i++ {
|
|
||||||
if json[i] == '"' {
|
|
||||||
// read the key
|
|
||||||
i, val, vesc, ok = parseString(json, i+1)
|
|
||||||
if !ok {
|
|
||||||
return i, false
|
|
||||||
}
|
|
||||||
if vesc {
|
|
||||||
// the value is escaped
|
|
||||||
key = unescape(val[1 : len(val)-1])
|
|
||||||
} else {
|
|
||||||
// just a plain old ascii key
|
|
||||||
key = val[1 : len(val)-1]
|
|
||||||
}
|
|
||||||
var hasMatch bool
|
|
||||||
var parsedVal bool
|
|
||||||
var valOrgIndex int
|
|
||||||
var valPathIndex int
|
|
||||||
for j := 0; j < len(key); j++ {
|
|
||||||
if key[j] == '.' {
|
|
||||||
// we need to look for keys with dot and ignore them.
|
|
||||||
if i, _, ok = parseAny(json, i, false); !ok {
|
|
||||||
return i, false
|
|
||||||
}
|
|
||||||
continue next_key
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var usedPaths int
|
|
||||||
// loop through paths and look for matches
|
|
||||||
for j := 0; j < len(paths); j++ {
|
|
||||||
if completed[j] {
|
|
||||||
usedPaths++
|
|
||||||
// ignore completed paths
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if level > 0 && (matches[j]>>(level-1))&1 == 0 {
|
|
||||||
// ignore unmatched paths
|
|
||||||
usedPaths++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// try to match the key to the path
|
|
||||||
// this is spaghetti code but the idea is to minimize
|
|
||||||
// calls and variable assignments when comparing the
|
|
||||||
// key to paths
|
|
||||||
if len(paths[j])-kplen >= len(key) {
|
|
||||||
i, k := kplen, 0
|
|
||||||
for ; k < len(key); k, i = k+1, i+1 {
|
|
||||||
if key[k] != paths[j][i] {
|
|
||||||
// no match
|
|
||||||
goto nomatch
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if i < len(paths[j]) {
|
|
||||||
if paths[j][i] == '.' {
|
|
||||||
// matched, but there are still more keys in path
|
|
||||||
goto match_not_atend
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(paths[j]) <= len(key) || kplen != 0 {
|
|
||||||
if len(paths[j]) != i {
|
|
||||||
goto nomatch
|
|
||||||
}
|
|
||||||
// matched and at the end of the path
|
|
||||||
goto match_atend
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// no match, jump to the nomatch label
|
|
||||||
goto nomatch
|
|
||||||
match_atend:
|
|
||||||
// found a match
|
|
||||||
// at the end of the path. we must take the value.
|
|
||||||
usedPaths++
|
|
||||||
if !parsedVal {
|
|
||||||
// the value has not been parsed yet. let's do so.
|
|
||||||
valOrgIndex = i // keep track of the current position.
|
|
||||||
i, results[j], ok = parseAny(json, i, true)
|
|
||||||
if !ok {
|
|
||||||
return i, false
|
|
||||||
}
|
|
||||||
parsedVal = true
|
|
||||||
valPathIndex = j
|
|
||||||
} else {
|
|
||||||
results[j] = results[valPathIndex]
|
|
||||||
}
|
|
||||||
// mark as complete
|
|
||||||
completed[j] = true
|
|
||||||
// jump over the match_not_atend label
|
|
||||||
goto nomatch
|
|
||||||
match_not_atend:
|
|
||||||
// found a match
|
|
||||||
// still in the middle of the path.
|
|
||||||
usedPaths++
|
|
||||||
// mark the path as matched
|
|
||||||
matches[j] |= 1 << level
|
|
||||||
if !hasMatch {
|
|
||||||
hasMatch = true
|
|
||||||
}
|
|
||||||
nomatch: // noop label
|
|
||||||
}
|
|
||||||
|
|
||||||
if !hasMatch && i < len(json) && json[i] == '}' {
|
|
||||||
return i + 1, true
|
|
||||||
}
|
|
||||||
if !parsedVal {
|
|
||||||
if hasMatch {
|
|
||||||
// we found a match and the value has not been parsed yet.
|
|
||||||
// let's find out if the next value type is an object.
|
|
||||||
for ; i < len(json); i++ {
|
|
||||||
if json[i] <= ' ' || json[i] == ':' {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if i < len(json) {
|
|
||||||
if json[i] == '{' {
|
|
||||||
// it's an object. let's go deeper
|
|
||||||
i, ok = parseGetMany(json, i+1, level+1, kplen+len(key)+1, paths, completed, matches, results)
|
|
||||||
if !ok {
|
|
||||||
return i, false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// not an object. just parse and ignore.
|
|
||||||
if i, _, ok = parseAny(json, i, false); !ok {
|
|
||||||
return i, false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Since there was no matches we can just parse the value and
|
|
||||||
// ignore the result.
|
|
||||||
if i, _, ok = parseAny(json, i, false); !ok {
|
|
||||||
return i, false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if hasMatch && len(results[valPathIndex].Raw) > 0 && results[valPathIndex].Raw[0] == '{' {
|
|
||||||
// The value was already parsed and the value type is an object.
|
|
||||||
// Rewind the json index and let's parse deeper.
|
|
||||||
i = valOrgIndex
|
|
||||||
for ; i < len(json); i++ {
|
|
||||||
if json[i] == '{' {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
i, ok = parseGetMany(json, i+1, level+1, kplen+len(key)+1, paths, completed, matches, results)
|
|
||||||
if !ok {
|
|
||||||
return i, false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if usedPaths == len(paths) {
|
|
||||||
// all paths have been used, either completed or matched.
|
|
||||||
// we should stop parsing this object to save CPU cycles.
|
|
||||||
if level > 0 && i < len(json) {
|
|
||||||
i, _ = parseSquash(json, i)
|
|
||||||
}
|
|
||||||
return i, true
|
|
||||||
}
|
|
||||||
} else if json[i] == '}' {
|
|
||||||
// reached the end of the object. end it here.
|
|
||||||
return i + 1, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return i, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call table for GetMany. Using an isolated function allows for allocating
|
|
||||||
// arrays with know capacities on the stack, as opposed to dynamically
|
|
||||||
// allocating on the heap. This can provide a tremendous performance boost
|
|
||||||
// by avoiding the GC.
|
|
||||||
func getMany8(json string, i int, paths []string) ([]Result, bool) {
|
|
||||||
const max = 8
|
|
||||||
var completed = make([]bool, 0, max)
|
|
||||||
var matches = make([]uint64, 0, max)
|
|
||||||
var results = make([]Result, 0, max)
|
|
||||||
completed = completed[0:len(paths):max]
|
|
||||||
matches = matches[0:len(paths):max]
|
|
||||||
results = results[0:len(paths):max]
|
|
||||||
_, ok := parseGetMany(json, i, 0, 0, paths, completed, matches, results)
|
|
||||||
return results, ok
|
|
||||||
}
|
|
||||||
func getMany16(json string, i int, paths []string) ([]Result, bool) {
|
|
||||||
const max = 16
|
|
||||||
var completed = make([]bool, 0, max)
|
|
||||||
var matches = make([]uint64, 0, max)
|
|
||||||
var results = make([]Result, 0, max)
|
|
||||||
completed = completed[0:len(paths):max]
|
|
||||||
matches = matches[0:len(paths):max]
|
|
||||||
results = results[0:len(paths):max]
|
|
||||||
_, ok := parseGetMany(json, i, 0, 0, paths, completed, matches, results)
|
|
||||||
return results, ok
|
|
||||||
}
|
|
||||||
func getMany32(json string, i int, paths []string) ([]Result, bool) {
|
|
||||||
const max = 32
|
|
||||||
var completed = make([]bool, 0, max)
|
|
||||||
var matches = make([]uint64, 0, max)
|
|
||||||
var results = make([]Result, 0, max)
|
|
||||||
completed = completed[0:len(paths):max]
|
|
||||||
matches = matches[0:len(paths):max]
|
|
||||||
results = results[0:len(paths):max]
|
|
||||||
_, ok := parseGetMany(json, i, 0, 0, paths, completed, matches, results)
|
|
||||||
return results, ok
|
|
||||||
}
|
|
||||||
func getMany64(json string, i int, paths []string) ([]Result, bool) {
|
|
||||||
const max = 64
|
|
||||||
var completed = make([]bool, 0, max)
|
|
||||||
var matches = make([]uint64, 0, max)
|
|
||||||
var results = make([]Result, 0, max)
|
|
||||||
completed = completed[0:len(paths):max]
|
|
||||||
matches = matches[0:len(paths):max]
|
|
||||||
results = results[0:len(paths):max]
|
|
||||||
_, ok := parseGetMany(json, i, 0, 0, paths, completed, matches, results)
|
|
||||||
return results, ok
|
|
||||||
}
|
|
||||||
func getMany128(json string, i int, paths []string) ([]Result, bool) {
|
|
||||||
const max = 128
|
|
||||||
var completed = make([]bool, 0, max)
|
|
||||||
var matches = make([]uint64, 0, max)
|
|
||||||
var results = make([]Result, 0, max)
|
|
||||||
completed = completed[0:len(paths):max]
|
|
||||||
matches = matches[0:len(paths):max]
|
|
||||||
results = results[0:len(paths):max]
|
|
||||||
_, ok := parseGetMany(json, i, 0, 0, paths, completed, matches, results)
|
|
||||||
return results, ok
|
|
||||||
}
|
|
||||||
func getMany256(json string, i int, paths []string) ([]Result, bool) {
|
|
||||||
const max = 256
|
|
||||||
var completed = make([]bool, 0, max)
|
|
||||||
var matches = make([]uint64, 0, max)
|
|
||||||
var results = make([]Result, 0, max)
|
|
||||||
completed = completed[0:len(paths):max]
|
|
||||||
matches = matches[0:len(paths):max]
|
|
||||||
results = results[0:len(paths):max]
|
|
||||||
_, ok := parseGetMany(json, i, 0, 0, paths, completed, matches, results)
|
|
||||||
return results, ok
|
|
||||||
}
|
|
||||||
func getMany512(json string, i int, paths []string) ([]Result, bool) {
|
|
||||||
const max = 512
|
|
||||||
var completed = make([]bool, 0, max)
|
|
||||||
var matches = make([]uint64, 0, max)
|
|
||||||
var results = make([]Result, 0, max)
|
|
||||||
completed = completed[0:len(paths):max]
|
|
||||||
matches = matches[0:len(paths):max]
|
|
||||||
results = results[0:len(paths):max]
|
|
||||||
_, ok := parseGetMany(json, i, 0, 0, paths, completed, matches, results)
|
|
||||||
return results, ok
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var fieldsmu sync.RWMutex
|
var fieldsmu sync.RWMutex
|
||||||
@ -2099,6 +1732,8 @@ var validate uintptr = 1
|
|||||||
|
|
||||||
// UnmarshalValidationEnabled provides the option to disable JSON validation
|
// UnmarshalValidationEnabled provides the option to disable JSON validation
|
||||||
// during the Unmarshal routine. Validation is enabled by default.
|
// during the Unmarshal routine. Validation is enabled by default.
|
||||||
|
//
|
||||||
|
// Deprecated: Use encoder/json.Unmarshal instead
|
||||||
func UnmarshalValidationEnabled(enabled bool) {
|
func UnmarshalValidationEnabled(enabled bool) {
|
||||||
if enabled {
|
if enabled {
|
||||||
atomic.StoreUintptr(&validate, 1)
|
atomic.StoreUintptr(&validate, 1)
|
||||||
@ -2113,6 +1748,8 @@ func UnmarshalValidationEnabled(enabled bool) {
|
|||||||
// gjson.Unmarshal will automatically attempt to convert JSON values to any Go
|
// gjson.Unmarshal will automatically attempt to convert JSON values to any Go
|
||||||
// type. For example, the JSON string "100" or the JSON number 100 can be equally
|
// type. For example, the JSON string "100" or the JSON number 100 can be equally
|
||||||
// assigned to Go string, int, byte, uint64, etc. This rule applies to all types.
|
// assigned to Go string, int, byte, uint64, etc. This rule applies to all types.
|
||||||
|
//
|
||||||
|
// Deprecated: Use encoder/json.Unmarshal instead
|
||||||
func Unmarshal(data []byte, v interface{}) error {
|
func Unmarshal(data []byte, v interface{}) error {
|
||||||
if atomic.LoadUintptr(&validate) == 1 {
|
if atomic.LoadUintptr(&validate) == 1 {
|
||||||
_, ok := validpayload(data, 0)
|
_, ok := validpayload(data, 0)
|
||||||
@ -2200,8 +1837,14 @@ func validobject(data []byte, i int) (outi int, ok bool) {
|
|||||||
if data[i] == '}' {
|
if data[i] == '}' {
|
||||||
return i + 1, true
|
return i + 1, true
|
||||||
}
|
}
|
||||||
|
i++
|
||||||
for ; i < len(data); i++ {
|
for ; i < len(data); i++ {
|
||||||
if data[i] == '"' {
|
switch data[i] {
|
||||||
|
default:
|
||||||
|
return i, false
|
||||||
|
case ' ', '\t', '\n', '\r':
|
||||||
|
continue
|
||||||
|
case '"':
|
||||||
goto key
|
goto key
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2382,11 +2025,31 @@ func validnull(data []byte, i int) (outi int, ok bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Valid returns true if the input is valid json.
|
// Valid returns true if the input is valid json.
|
||||||
|
//
|
||||||
|
// if !gjson.Valid(json) {
|
||||||
|
// return errors.New("invalid json")
|
||||||
|
// }
|
||||||
|
// value := gjson.Get(json, "name.last")
|
||||||
|
//
|
||||||
func Valid(json string) bool {
|
func Valid(json string) bool {
|
||||||
_, ok := validpayload([]byte(json), 0)
|
_, ok := validpayload([]byte(json), 0)
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ValidBytes returns true if the input is valid json.
|
||||||
|
//
|
||||||
|
// if !gjson.Valid(json) {
|
||||||
|
// return errors.New("invalid json")
|
||||||
|
// }
|
||||||
|
// value := gjson.Get(json, "name.last")
|
||||||
|
//
|
||||||
|
// If working with bytes, this method preferred over Valid(string(data))
|
||||||
|
//
|
||||||
|
func ValidBytes(json []byte) bool {
|
||||||
|
_, ok := validpayload(json, 0)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
func parseUint(s string) (n uint64, ok bool) {
|
func parseUint(s string) (n uint64, ok bool) {
|
||||||
var i int
|
var i int
|
||||||
if i == len(s) {
|
if i == len(s) {
|
||||||
|
10
vendor/github.com/tidwall/gjson/gjson_gae.go
generated
vendored
Normal file
10
vendor/github.com/tidwall/gjson/gjson_gae.go
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
//+build appengine
|
||||||
|
|
||||||
|
package gjson
|
||||||
|
|
||||||
|
func getBytes(json []byte, path string) Result {
|
||||||
|
return Get(string(json), path)
|
||||||
|
}
|
||||||
|
func fillIndex(json string, c *parseContext) {
|
||||||
|
// noop. Use zero for the Index value.
|
||||||
|
}
|
73
vendor/github.com/tidwall/gjson/gjson_ngae.go
generated
vendored
Normal file
73
vendor/github.com/tidwall/gjson/gjson_ngae.go
generated
vendored
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
//+build !appengine
|
||||||
|
|
||||||
|
package gjson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// getBytes casts the input json bytes to a string and safely returns the
|
||||||
|
// results as uniquely allocated data. This operation is intended to minimize
|
||||||
|
// copies and allocations for the large json string->[]byte.
|
||||||
|
func getBytes(json []byte, path string) Result {
|
||||||
|
var result Result
|
||||||
|
if json != nil {
|
||||||
|
// unsafe cast to string
|
||||||
|
result = Get(*(*string)(unsafe.Pointer(&json)), path)
|
||||||
|
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 {
|
||||||
|
// raw is nil
|
||||||
|
result.Raw = ""
|
||||||
|
} else {
|
||||||
|
// raw has data, safely copy the slice header to a string
|
||||||
|
result.Raw = string(*(*[]byte)(unsafe.Pointer(&rawh)))
|
||||||
|
}
|
||||||
|
result.Str = ""
|
||||||
|
} else if rawh.Data == 0 {
|
||||||
|
// raw is nil
|
||||||
|
result.Raw = ""
|
||||||
|
// str has data, safely copy the slice header to a string
|
||||||
|
result.Str = string(*(*[]byte)(unsafe.Pointer(&strh)))
|
||||||
|
} else if strh.Data >= rawh.Data &&
|
||||||
|
int(strh.Data)+strh.Len <= int(rawh.Data)+rawh.Len {
|
||||||
|
// Str is a substring of Raw.
|
||||||
|
start := int(strh.Data - rawh.Data)
|
||||||
|
// safely copy the raw slice header
|
||||||
|
result.Raw = string(*(*[]byte)(unsafe.Pointer(&rawh)))
|
||||||
|
// substring the raw
|
||||||
|
result.Str = result.Raw[start : start+strh.Len]
|
||||||
|
} else {
|
||||||
|
// safely copy both the raw and str slice headers to strings
|
||||||
|
result.Raw = string(*(*[]byte)(unsafe.Pointer(&rawh)))
|
||||||
|
result.Str = string(*(*[]byte)(unsafe.Pointer(&strh)))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// fillIndex finds the position of Raw data and assigns it to the Index field
|
||||||
|
// of the resulting value. If the position cannot be found then Index zero is
|
||||||
|
// used instead.
|
||||||
|
func fillIndex(json string, c *parseContext) {
|
||||||
|
if len(c.value.Raw) > 0 && !c.calcd {
|
||||||
|
jhdr := *(*reflect.StringHeader)(unsafe.Pointer(&json))
|
||||||
|
rhdr := *(*reflect.StringHeader)(unsafe.Pointer(&(c.value.Raw)))
|
||||||
|
c.value.Index = int(rhdr.Data - jhdr.Data)
|
||||||
|
if c.value.Index < 0 || c.value.Index >= len(json) {
|
||||||
|
c.value.Index = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
466
vendor/github.com/tidwall/gjson/gjson_test.go
generated
vendored
466
vendor/github.com/tidwall/gjson/gjson_test.go
generated
vendored
@ -7,6 +7,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@ -147,7 +148,7 @@ func TestTimeResult(t *testing.T) {
|
|||||||
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("valse").Bool() == false)
|
assert(t, Parse("false").Bool() == false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestManyVariousPathCounts(t *testing.T) {
|
func TestManyVariousPathCounts(t *testing.T) {
|
||||||
@ -478,7 +479,8 @@ func TestBasic4(t *testing.T) {
|
|||||||
}
|
}
|
||||||
token = get(basicJSON, "arr.#")
|
token = get(basicJSON, "arr.#")
|
||||||
if token.String() != "6" {
|
if token.String() != "6" {
|
||||||
t.Fatal("expecting '6'", "got", token.String())
|
fmt.Printf("%#v\n", token)
|
||||||
|
t.Fatal("expecting 6", "got", token.String())
|
||||||
}
|
}
|
||||||
token = get(basicJSON, "arr.3.hello")
|
token = get(basicJSON, "arr.3.hello")
|
||||||
if token.String() != "world" {
|
if token.String() != "world" {
|
||||||
@ -969,80 +971,82 @@ func TestUnmarshal(t *testing.T) {
|
|||||||
assert(t, str == Get(complicatedJSON, "LeftOut").String())
|
assert(t, str == Get(complicatedJSON, "LeftOut").String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func testvalid(json string, expect bool) {
|
func testvalid(t *testing.T, json string, expect bool) {
|
||||||
|
t.Helper()
|
||||||
_, ok := validpayload([]byte(json), 0)
|
_, ok := validpayload([]byte(json), 0)
|
||||||
if ok != expect {
|
if ok != expect {
|
||||||
panic("mismatch")
|
t.Fatal("mismatch")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidBasic(t *testing.T) {
|
func TestValidBasic(t *testing.T) {
|
||||||
testvalid("0", true)
|
testvalid(t, "0", true)
|
||||||
testvalid("00", false)
|
testvalid(t, "00", false)
|
||||||
testvalid("-00", false)
|
testvalid(t, "-00", false)
|
||||||
testvalid("-.", false)
|
testvalid(t, "-.", false)
|
||||||
testvalid("0.0", true)
|
testvalid(t, "0.0", true)
|
||||||
testvalid("10.0", true)
|
testvalid(t, "10.0", true)
|
||||||
testvalid("10e1", true)
|
testvalid(t, "10e1", true)
|
||||||
testvalid("10EE", false)
|
testvalid(t, "10EE", false)
|
||||||
testvalid("10E-", false)
|
testvalid(t, "10E-", false)
|
||||||
testvalid("10E+", false)
|
testvalid(t, "10E+", false)
|
||||||
testvalid("10E123", true)
|
testvalid(t, "10E123", true)
|
||||||
testvalid("10E-123", true)
|
testvalid(t, "10E-123", true)
|
||||||
testvalid("10E-0123", true)
|
testvalid(t, "10E-0123", true)
|
||||||
testvalid("", false)
|
testvalid(t, "", false)
|
||||||
testvalid(" ", false)
|
testvalid(t, " ", false)
|
||||||
testvalid("{}", true)
|
testvalid(t, "{}", true)
|
||||||
testvalid("{", false)
|
testvalid(t, "{", false)
|
||||||
testvalid("-", false)
|
testvalid(t, "-", false)
|
||||||
testvalid("-1", true)
|
testvalid(t, "-1", true)
|
||||||
testvalid("-1.", false)
|
testvalid(t, "-1.", false)
|
||||||
testvalid("-1.0", true)
|
testvalid(t, "-1.0", true)
|
||||||
testvalid(" -1.0", true)
|
testvalid(t, " -1.0", true)
|
||||||
testvalid(" -1.0 ", true)
|
testvalid(t, " -1.0 ", true)
|
||||||
testvalid("-1.0 ", true)
|
testvalid(t, "-1.0 ", true)
|
||||||
testvalid("-1.0 i", false)
|
testvalid(t, "-1.0 i", false)
|
||||||
testvalid("-1.0 i", false)
|
testvalid(t, "-1.0 i", false)
|
||||||
testvalid("true", true)
|
testvalid(t, "true", true)
|
||||||
testvalid(" true", true)
|
testvalid(t, " true", true)
|
||||||
testvalid(" true ", true)
|
testvalid(t, " true ", true)
|
||||||
testvalid(" True ", false)
|
testvalid(t, " True ", false)
|
||||||
testvalid(" tru", false)
|
testvalid(t, " tru", false)
|
||||||
testvalid("false", true)
|
testvalid(t, "false", true)
|
||||||
testvalid(" false", true)
|
testvalid(t, " false", true)
|
||||||
testvalid(" false ", true)
|
testvalid(t, " false ", true)
|
||||||
testvalid(" False ", false)
|
testvalid(t, " False ", false)
|
||||||
testvalid(" fals", false)
|
testvalid(t, " fals", false)
|
||||||
testvalid("null", true)
|
testvalid(t, "null", true)
|
||||||
testvalid(" null", true)
|
testvalid(t, " null", true)
|
||||||
testvalid(" null ", true)
|
testvalid(t, " null ", true)
|
||||||
testvalid(" Null ", false)
|
testvalid(t, " Null ", false)
|
||||||
testvalid(" nul", false)
|
testvalid(t, " nul", false)
|
||||||
testvalid(" []", true)
|
testvalid(t, " []", true)
|
||||||
testvalid(" [true]", true)
|
testvalid(t, " [true]", true)
|
||||||
testvalid(" [ true, null ]", true)
|
testvalid(t, " [ true, null ]", true)
|
||||||
testvalid(" [ true,]", false)
|
testvalid(t, " [ true,]", false)
|
||||||
testvalid(`{"hello":"world"}`, true)
|
testvalid(t, `{"hello":"world"}`, true)
|
||||||
testvalid(`{ "hello": "world" }`, true)
|
testvalid(t, `{ "hello": "world" }`, true)
|
||||||
testvalid(`{ "hello": "world", }`, false)
|
testvalid(t, `{ "hello": "world", }`, false)
|
||||||
testvalid(`{"a":"b",}`, false)
|
testvalid(t, `{"a":"b",}`, false)
|
||||||
testvalid(`{"a":"b","a"}`, false)
|
testvalid(t, `{"a":"b","a"}`, false)
|
||||||
testvalid(`{"a":"b","a":}`, false)
|
testvalid(t, `{"a":"b","a":}`, false)
|
||||||
testvalid(`{"a":"b","a":1}`, true)
|
testvalid(t, `{"a":"b","a":1}`, true)
|
||||||
testvalid(`{"a":"b","a": 1, "c":{"hi":"there"} }`, true)
|
testvalid(t, `{"a":"b",2"1":2}`, false)
|
||||||
testvalid(`{"a":"b","a": 1, "c":{"hi":"there", "easy":["going",{"mixed":"bag"}]} }`, true)
|
testvalid(t, `{"a":"b","a": 1, "c":{"hi":"there"} }`, true)
|
||||||
testvalid(`""`, true)
|
testvalid(t, `{"a":"b","a": 1, "c":{"hi":"there", "easy":["going",{"mixed":"bag"}]} }`, true)
|
||||||
testvalid(`"`, false)
|
testvalid(t, `""`, true)
|
||||||
testvalid(`"\n"`, true)
|
testvalid(t, `"`, false)
|
||||||
testvalid(`"\"`, false)
|
testvalid(t, `"\n"`, true)
|
||||||
testvalid(`"\\"`, true)
|
testvalid(t, `"\"`, false)
|
||||||
testvalid(`"a\\b"`, true)
|
testvalid(t, `"\\"`, true)
|
||||||
testvalid(`"a\\b\\\"a"`, true)
|
testvalid(t, `"a\\b"`, true)
|
||||||
testvalid(`"a\\b\\\uFFAAa"`, true)
|
testvalid(t, `"a\\b\\\"a"`, true)
|
||||||
testvalid(`"a\\b\\\uFFAZa"`, false)
|
testvalid(t, `"a\\b\\\uFFAAa"`, true)
|
||||||
testvalid(`"a\\b\\\uFFA"`, false)
|
testvalid(t, `"a\\b\\\uFFAZa"`, false)
|
||||||
testvalid(string(complicatedJSON), true)
|
testvalid(t, `"a\\b\\\uFFA"`, false)
|
||||||
testvalid(string(exampleJSON), true)
|
testvalid(t, string(complicatedJSON), true)
|
||||||
|
testvalid(t, string(exampleJSON), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
var jsonchars = []string{"{", "[", ",", ":", "}", "]", "1", "0", "true", "false", "null", `""`, `"\""`, `"a"`}
|
var jsonchars = []string{"{", "[", ",", ":", "}", "]", "1", "0", "true", "false", "null", `""`, `"\""`, `"a"`}
|
||||||
@ -1101,3 +1105,325 @@ func TestGetMany48(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestResultRawForLiteral(t *testing.T) {
|
||||||
|
for _, lit := range []string{"null", "true", "false"} {
|
||||||
|
result := Parse(lit)
|
||||||
|
if result.Raw != lit {
|
||||||
|
t.Fatalf("expected '%v', got '%v'", lit, result.Raw)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNullArray(t *testing.T) {
|
||||||
|
n := len(Get(`{"data":null}`, "data").Array())
|
||||||
|
if n != 0 {
|
||||||
|
t.Fatalf("expected '%v', got '%v'", 0, n)
|
||||||
|
}
|
||||||
|
n = len(Get(`{}`, "data").Array())
|
||||||
|
if n != 0 {
|
||||||
|
t.Fatalf("expected '%v', got '%v'", 0, n)
|
||||||
|
}
|
||||||
|
n = len(Get(`{"data":[]}`, "data").Array())
|
||||||
|
if n != 0 {
|
||||||
|
t.Fatalf("expected '%v', got '%v'", 0, n)
|
||||||
|
}
|
||||||
|
n = len(Get(`{"data":[null]}`, "data").Array())
|
||||||
|
if n != 1 {
|
||||||
|
t.Fatalf("expected '%v', got '%v'", 1, n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRandomGetMany(t *testing.T) {
|
||||||
|
start := time.Now()
|
||||||
|
for time.Since(start) < time.Second*3 {
|
||||||
|
testRandomGetMany(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func testRandomGetMany(t *testing.T) {
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
json, keys := randomJSON()
|
||||||
|
for _, key := range keys {
|
||||||
|
r := Get(json, key)
|
||||||
|
if !r.Exists() {
|
||||||
|
t.Fatal("should exist")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rkeysi := rand.Perm(len(keys))
|
||||||
|
rkeysn := 1 + rand.Int()%32
|
||||||
|
if len(rkeysi) > rkeysn {
|
||||||
|
rkeysi = rkeysi[:rkeysn]
|
||||||
|
}
|
||||||
|
var rkeys []string
|
||||||
|
for i := 0; i < len(rkeysi); i++ {
|
||||||
|
rkeys = append(rkeys, keys[rkeysi[i]])
|
||||||
|
}
|
||||||
|
mres1 := GetMany(json, rkeys...)
|
||||||
|
var mres2 []Result
|
||||||
|
for _, rkey := range rkeys {
|
||||||
|
mres2 = append(mres2, Get(json, rkey))
|
||||||
|
}
|
||||||
|
if len(mres1) != len(mres2) {
|
||||||
|
t.Fatalf("expected %d, got %d", len(mres2), len(mres1))
|
||||||
|
}
|
||||||
|
for i := 0; i < len(mres1); i++ {
|
||||||
|
mres1[i].Index = 0
|
||||||
|
mres2[i].Index = 0
|
||||||
|
v1 := fmt.Sprintf("%#v", mres1[i])
|
||||||
|
v2 := fmt.Sprintf("%#v", mres2[i])
|
||||||
|
if v1 != v2 {
|
||||||
|
t.Fatalf("\nexpected %s\n"+
|
||||||
|
" got %s", v2, v1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIssue54(t *testing.T) {
|
||||||
|
var r []Result
|
||||||
|
json := `{"MarketName":null,"Nounce":6115}`
|
||||||
|
r = GetMany(json, "Nounce", "Buys", "Sells", "Fills")
|
||||||
|
if strings.Replace(fmt.Sprintf("%v", r), " ", "", -1) != "[6115]" {
|
||||||
|
t.Fatalf("expected '%v', got '%v'", "[6115]", strings.Replace(fmt.Sprintf("%v", r), " ", "", -1))
|
||||||
|
}
|
||||||
|
r = GetMany(json, "Nounce", "Buys", "Sells")
|
||||||
|
if strings.Replace(fmt.Sprintf("%v", r), " ", "", -1) != "[6115]" {
|
||||||
|
t.Fatalf("expected '%v', got '%v'", "[6115]", strings.Replace(fmt.Sprintf("%v", r), " ", "", -1))
|
||||||
|
}
|
||||||
|
r = GetMany(json, "Nounce")
|
||||||
|
if strings.Replace(fmt.Sprintf("%v", r), " ", "", -1) != "[6115]" {
|
||||||
|
t.Fatalf("expected '%v', got '%v'", "[6115]", strings.Replace(fmt.Sprintf("%v", r), " ", "", -1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func randomString() string {
|
||||||
|
var key string
|
||||||
|
N := 1 + rand.Int()%16
|
||||||
|
for i := 0; i < N; i++ {
|
||||||
|
r := rand.Int() % 62
|
||||||
|
if r < 10 {
|
||||||
|
key += string(byte('0' + r))
|
||||||
|
} else if r-10 < 26 {
|
||||||
|
key += string(byte('a' + r - 10))
|
||||||
|
} else {
|
||||||
|
key += string(byte('A' + r - 10 - 26))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return `"` + key + `"`
|
||||||
|
}
|
||||||
|
func randomBool() string {
|
||||||
|
switch rand.Int() % 2 {
|
||||||
|
default:
|
||||||
|
return "false"
|
||||||
|
case 1:
|
||||||
|
return "true"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func randomNumber() string {
|
||||||
|
return strconv.FormatInt(int64(rand.Int()%1000000), 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
func randomObjectOrArray(keys []string, prefix string, array bool, depth int) (string, []string) {
|
||||||
|
N := 5 + rand.Int()%5
|
||||||
|
var json string
|
||||||
|
if array {
|
||||||
|
json = "["
|
||||||
|
} else {
|
||||||
|
json = "{"
|
||||||
|
}
|
||||||
|
for i := 0; i < N; i++ {
|
||||||
|
if i > 0 {
|
||||||
|
json += ","
|
||||||
|
}
|
||||||
|
var pkey string
|
||||||
|
if array {
|
||||||
|
pkey = prefix + "." + strconv.FormatInt(int64(i), 10)
|
||||||
|
} else {
|
||||||
|
key := randomString()
|
||||||
|
pkey = prefix + "." + key[1:len(key)-1]
|
||||||
|
json += key + `:`
|
||||||
|
}
|
||||||
|
keys = append(keys, pkey[1:])
|
||||||
|
var kind int
|
||||||
|
if depth == 5 {
|
||||||
|
kind = rand.Int() % 4
|
||||||
|
} else {
|
||||||
|
kind = rand.Int() % 6
|
||||||
|
}
|
||||||
|
switch kind {
|
||||||
|
case 0:
|
||||||
|
json += randomString()
|
||||||
|
case 1:
|
||||||
|
json += randomBool()
|
||||||
|
case 2:
|
||||||
|
json += "null"
|
||||||
|
case 3:
|
||||||
|
json += randomNumber()
|
||||||
|
case 4:
|
||||||
|
var njson string
|
||||||
|
njson, keys = randomObjectOrArray(keys, pkey, true, depth+1)
|
||||||
|
json += njson
|
||||||
|
case 5:
|
||||||
|
var njson string
|
||||||
|
njson, keys = randomObjectOrArray(keys, pkey, false, depth+1)
|
||||||
|
json += njson
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if array {
|
||||||
|
json += "]"
|
||||||
|
} else {
|
||||||
|
json += "}"
|
||||||
|
}
|
||||||
|
return json, keys
|
||||||
|
}
|
||||||
|
|
||||||
|
func randomJSON() (json string, keys []string) {
|
||||||
|
return randomObjectOrArray(nil, "", false, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIssue55(t *testing.T) {
|
||||||
|
json := `{"one": {"two": 2, "three": 3}, "four": 4, "five": 5}`
|
||||||
|
results := GetMany(json, "four", "five", "one.two", "one.six")
|
||||||
|
expected := []string{"4", "5", "2", ""}
|
||||||
|
for i, r := range results {
|
||||||
|
if r.String() != expected[i] {
|
||||||
|
t.Fatalf("expected %v, got %v", expected[i], r.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestIssue58(t *testing.T) {
|
||||||
|
json := `{"data":[{"uid": 1},{"uid": 2}]}`
|
||||||
|
res := Get(json, `data.#[uid!=1]`).Raw
|
||||||
|
if res != `{"uid": 2}` {
|
||||||
|
t.Fatalf("expected '%v', got '%v'", `{"uid": 1}`, res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestObjectGrouping(t *testing.T) {
|
||||||
|
json := `
|
||||||
|
[
|
||||||
|
true,
|
||||||
|
{"name":"tom"},
|
||||||
|
false,
|
||||||
|
{"name":"janet"},
|
||||||
|
null
|
||||||
|
]
|
||||||
|
`
|
||||||
|
res := Get(json, "#.name")
|
||||||
|
if res.String() != `["tom","janet"]` {
|
||||||
|
t.Fatalf("expected '%v', got '%v'", `["tom","janet"]`, res.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJSONLines(t *testing.T) {
|
||||||
|
json := `
|
||||||
|
true
|
||||||
|
false
|
||||||
|
{"name":"tom"}
|
||||||
|
[1,2,3,4,5]
|
||||||
|
{"name":"janet"}
|
||||||
|
null
|
||||||
|
12930.1203
|
||||||
|
`
|
||||||
|
paths := []string{"..#", "..0", "..2.name", "..#.name", "..6", "..7"}
|
||||||
|
ress := []string{"7", "true", "tom", `["tom","janet"]`, "12930.1203", ""}
|
||||||
|
for i, path := range paths {
|
||||||
|
res := Get(json, path)
|
||||||
|
if res.String() != ress[i] {
|
||||||
|
t.Fatalf("expected '%v', got '%v'", ress[i], res.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
json = `
|
||||||
|
{"name": "Gilbert", "wins": [["straight", "7♣"], ["one pair", "10♥"]]}
|
||||||
|
{"name": "Alexa", "wins": [["two pair", "4♠"], ["two pair", "9♠"]]}
|
||||||
|
{"name": "May", "wins": []}
|
||||||
|
{"name": "Deloise", "wins": [["three of a kind", "5♣"]]}
|
||||||
|
`
|
||||||
|
|
||||||
|
var i int
|
||||||
|
lines := strings.Split(strings.TrimSpace(json), "\n")
|
||||||
|
ForEachLine(json, func(line Result) bool {
|
||||||
|
if line.Raw != lines[i] {
|
||||||
|
t.Fatalf("expected '%v', got '%v'", lines[i], line.Raw)
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
if i != 4 {
|
||||||
|
t.Fatalf("expected '%v', got '%v'", 4, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNumUint64String(t *testing.T) {
|
||||||
|
i := 9007199254740993 //2^53 + 1
|
||||||
|
j := fmt.Sprintf(`{"data": [ %d, "hello" ] }`, i)
|
||||||
|
res := Get(j, "data.0")
|
||||||
|
if res.String() != "9007199254740993" {
|
||||||
|
t.Fatalf("expected '%v', got '%v'", "9007199254740993", res.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNumInt64String(t *testing.T) {
|
||||||
|
i := -9007199254740993
|
||||||
|
j := fmt.Sprintf(`{"data":[ "hello", %d ]}`, i)
|
||||||
|
res := Get(j, "data.1")
|
||||||
|
if res.String() != "-9007199254740993" {
|
||||||
|
t.Fatalf("expected '%v', got '%v'", "-9007199254740993", res.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNumBigString(t *testing.T) {
|
||||||
|
i := "900719925474099301239109123101" // very big
|
||||||
|
j := fmt.Sprintf(`{"data":[ "hello", "%s" ]}`, i)
|
||||||
|
res := Get(j, "data.1")
|
||||||
|
if res.String() != "900719925474099301239109123101" {
|
||||||
|
t.Fatalf("expected '%v', got '%v'", "900719925474099301239109123101", res.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNumFloatString(t *testing.T) {
|
||||||
|
i := -9007199254740993
|
||||||
|
j := fmt.Sprintf(`{"data":[ "hello", %d ]}`, i) //No quotes around value!!
|
||||||
|
res := Get(j, "data.1")
|
||||||
|
if res.String() != "-9007199254740993" {
|
||||||
|
t.Fatalf("expected '%v', got '%v'", "-9007199254740993", res.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDuplicateKeys(t *testing.T) {
|
||||||
|
// this is vaild json according to the JSON spec
|
||||||
|
var json = `{"name": "Alex","name": "Peter"}`
|
||||||
|
if Parse(json).Get("name").String() !=
|
||||||
|
Parse(json).Map()["name"].String() {
|
||||||
|
t.Fatalf("expected '%v', got '%v'",
|
||||||
|
Parse(json).Get("name").String(),
|
||||||
|
Parse(json).Map()["name"].String(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if !Valid(json) {
|
||||||
|
t.Fatal("should be valid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayValues(t *testing.T) {
|
||||||
|
var json = `{"array": ["PERSON1","PERSON2",0],}`
|
||||||
|
values := Get(json, "array").Array()
|
||||||
|
var output string
|
||||||
|
for i, val := range values {
|
||||||
|
if i > 0 {
|
||||||
|
output += "\n"
|
||||||
|
}
|
||||||
|
output += fmt.Sprintf("%#v", val)
|
||||||
|
}
|
||||||
|
expect := strings.Join([]string{
|
||||||
|
`gjson.Result{Type:3, Raw:"\"PERSON1\"", Str:"PERSON1", Num:0, Index:0}`,
|
||||||
|
`gjson.Result{Type:3, Raw:"\"PERSON2\"", Str:"PERSON2", Num:0, Index:0}`,
|
||||||
|
`gjson.Result{Type:2, Raw:"0", Str:"", Num:0, Index:0}`,
|
||||||
|
}, "\n")
|
||||||
|
if output != expect {
|
||||||
|
t.Fatalf("expected '%v', got '%v'", expect, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
8
vendor/github.com/tidwall/sjson/README.md
generated
vendored
8
vendor/github.com/tidwall/sjson/README.md
generated
vendored
@ -7,12 +7,12 @@
|
|||||||
<a href="https://godoc.org/github.com/tidwall/sjson"><img src="https://img.shields.io/badge/api-reference-blue.svg?style=flat-square" alt="GoDoc"></a>
|
<a href="https://godoc.org/github.com/tidwall/sjson"><img src="https://img.shields.io/badge/api-reference-blue.svg?style=flat-square" alt="GoDoc"></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">set a json value quickly</a></p>
|
<p align="center">set a json value quickly</p>
|
||||||
|
|
||||||
SJSON is a Go package that provides a [very fast](#performance) and simple way to set a value in a json document. The purpose for this library is to provide efficient json updating for the [SummitDB](https://github.com/tidwall/summitdb) project.
|
SJSON is a Go package that provides a [very fast](#performance) and simple way to set a value in a json document. The purpose for this library is to provide efficient json updating for the [SummitDB](https://github.com/tidwall/summitdb) project.
|
||||||
For quickly retrieving json values check out [GJSON](https://github.com/tidwall/gjson).
|
For quickly retrieving json values check out [GJSON](https://github.com/tidwall/gjson).
|
||||||
|
|
||||||
For a command line interface check out [JSONed](https://github.com/tidwall/jsoned).
|
For a command line interface check out [JJ](https://github.com/tidwall/jj).
|
||||||
|
|
||||||
Getting Started
|
Getting Started
|
||||||
===============
|
===============
|
||||||
@ -59,7 +59,7 @@ Path syntax
|
|||||||
-----------
|
-----------
|
||||||
|
|
||||||
A path is a series of keys separated by a dot.
|
A path is a series of keys separated by a dot.
|
||||||
The dot and colon characters can be escaped with '\'.
|
The dot and colon characters can be escaped with ``\``.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@ -268,7 +268,7 @@ widget.image.hOffset
|
|||||||
widget.text.onMouseUp
|
widget.text.onMouseUp
|
||||||
```
|
```
|
||||||
|
|
||||||
*These benchmarks were run on a MacBook Pro 15" 2.8 GHz Intel Core i7 using Go 1.7.*
|
*These benchmarks were run on a MacBook Pro 15" 2.8 GHz Intel Core i7 using Go 1.7 and can be be found [here](https://github.com/tidwall/sjson-benchmarks)*.
|
||||||
|
|
||||||
## Contact
|
## Contact
|
||||||
Josh Baker [@tidwall](http://twitter.com/tidwall)
|
Josh Baker [@tidwall](http://twitter.com/tidwall)
|
||||||
|
197
vendor/github.com/tidwall/sjson/sjson.go
generated
vendored
197
vendor/github.com/tidwall/sjson/sjson.go
generated
vendored
@ -3,9 +3,7 @@ package sjson
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
jsongo "encoding/json"
|
jsongo "encoding/json"
|
||||||
"reflect"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/tidwall/gjson"
|
"github.com/tidwall/gjson"
|
||||||
)
|
)
|
||||||
@ -36,6 +34,7 @@ type Options struct {
|
|||||||
|
|
||||||
type pathResult struct {
|
type pathResult struct {
|
||||||
part string // current key part
|
part string // current key part
|
||||||
|
gpart string // gjson get part
|
||||||
path string // remaining path
|
path string // remaining path
|
||||||
force bool // force a string key
|
force bool // force a string key
|
||||||
more bool // there is more path to parse
|
more bool // there is more path to parse
|
||||||
@ -50,6 +49,7 @@ func parsePath(path string) (pathResult, error) {
|
|||||||
for i := 0; i < len(path); i++ {
|
for i := 0; i < len(path); i++ {
|
||||||
if path[i] == '.' {
|
if path[i] == '.' {
|
||||||
r.part = path[:i]
|
r.part = path[:i]
|
||||||
|
r.gpart = path[:i]
|
||||||
r.path = path[i+1:]
|
r.path = path[i+1:]
|
||||||
r.more = true
|
r.more = true
|
||||||
return r, nil
|
return r, nil
|
||||||
@ -63,19 +63,24 @@ func parsePath(path string) (pathResult, error) {
|
|||||||
// go into escape mode. this is a slower path that
|
// go into escape mode. this is a slower path that
|
||||||
// strips off the escape character from the part.
|
// strips off the escape character from the part.
|
||||||
epart := []byte(path[:i])
|
epart := []byte(path[:i])
|
||||||
|
gpart := []byte(path[:i+1])
|
||||||
i++
|
i++
|
||||||
if i < len(path) {
|
if i < len(path) {
|
||||||
epart = append(epart, path[i])
|
epart = append(epart, path[i])
|
||||||
|
gpart = append(gpart, path[i])
|
||||||
i++
|
i++
|
||||||
for ; i < len(path); i++ {
|
for ; i < len(path); i++ {
|
||||||
if path[i] == '\\' {
|
if path[i] == '\\' {
|
||||||
|
gpart = append(gpart, '\\')
|
||||||
i++
|
i++
|
||||||
if i < len(path) {
|
if i < len(path) {
|
||||||
epart = append(epart, path[i])
|
epart = append(epart, path[i])
|
||||||
|
gpart = append(gpart, path[i])
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
} else if path[i] == '.' {
|
} else if path[i] == '.' {
|
||||||
r.part = string(epart)
|
r.part = string(epart)
|
||||||
|
r.gpart = string(gpart)
|
||||||
r.path = path[i+1:]
|
r.path = path[i+1:]
|
||||||
r.more = true
|
r.more = true
|
||||||
return r, nil
|
return r, nil
|
||||||
@ -87,20 +92,23 @@ func parsePath(path string) (pathResult, error) {
|
|||||||
"array access character not allowed in path"}
|
"array access character not allowed in path"}
|
||||||
}
|
}
|
||||||
epart = append(epart, path[i])
|
epart = append(epart, path[i])
|
||||||
|
gpart = append(gpart, path[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// append the last part
|
// append the last part
|
||||||
r.part = string(epart)
|
r.part = string(epart)
|
||||||
|
r.gpart = string(gpart)
|
||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
r.part = path
|
r.part = path
|
||||||
|
r.gpart = path
|
||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func mustMarshalString(s string) bool {
|
func mustMarshalString(s string) bool {
|
||||||
for i := 0; i < len(s); i++ {
|
for i := 0; i < len(s); i++ {
|
||||||
if s[i] < ' ' || s[i] > 0x7f || s[i] == '"' {
|
if s[i] < ' ' || s[i] > 0x7f || s[i] == '"' || (s[i] == '\\' && i == len(s)-1) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -208,7 +216,7 @@ loop:
|
|||||||
for ; i >= 0; i-- {
|
for ; i >= 0; i-- {
|
||||||
if buf[i] == '"' {
|
if buf[i] == '"' {
|
||||||
i--
|
i--
|
||||||
if i >= 0 && i == '\\' {
|
if i >= 0 && buf[i] == '\\' {
|
||||||
i--
|
i--
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -249,7 +257,7 @@ func appendRawPaths(buf []byte, jstr string, paths []pathResult, raw string,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !found {
|
if !found {
|
||||||
res = gjson.Get(jstr, paths[0].part)
|
res = gjson.Get(jstr, paths[0].gpart)
|
||||||
}
|
}
|
||||||
if res.Index > 0 {
|
if res.Index > 0 {
|
||||||
if len(paths) > 1 {
|
if len(paths) > 1 {
|
||||||
@ -400,72 +408,6 @@ func isOptimisticPath(path string) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func set(jstr, path, raw string,
|
|
||||||
stringify, del, optimistic, inplace bool) ([]byte, error) {
|
|
||||||
if path == "" {
|
|
||||||
return nil, &errorType{"path cannot be empty"}
|
|
||||||
}
|
|
||||||
if !del && optimistic && isOptimisticPath(path) {
|
|
||||||
res := gjson.Get(jstr, path)
|
|
||||||
if res.Exists() && res.Index > 0 {
|
|
||||||
sz := len(jstr) - len(res.Raw) + len(raw)
|
|
||||||
if stringify {
|
|
||||||
sz += 2
|
|
||||||
}
|
|
||||||
if inplace && sz <= len(jstr) {
|
|
||||||
if !stringify || !mustMarshalString(raw) {
|
|
||||||
jsonh := *(*reflect.StringHeader)(unsafe.Pointer(&jstr))
|
|
||||||
jsonbh := reflect.SliceHeader{
|
|
||||||
Data: jsonh.Data, Len: jsonh.Len, Cap: jsonh.Len}
|
|
||||||
jbytes := *(*[]byte)(unsafe.Pointer(&jsonbh))
|
|
||||||
if stringify {
|
|
||||||
jbytes[res.Index] = '"'
|
|
||||||
copy(jbytes[res.Index+1:], []byte(raw))
|
|
||||||
jbytes[res.Index+1+len(raw)] = '"'
|
|
||||||
copy(jbytes[res.Index+1+len(raw)+1:],
|
|
||||||
jbytes[res.Index+len(res.Raw):])
|
|
||||||
} else {
|
|
||||||
copy(jbytes[res.Index:], []byte(raw))
|
|
||||||
copy(jbytes[res.Index+len(raw):],
|
|
||||||
jbytes[res.Index+len(res.Raw):])
|
|
||||||
}
|
|
||||||
return jbytes[:sz], nil
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
buf := make([]byte, 0, sz)
|
|
||||||
buf = append(buf, jstr[:res.Index]...)
|
|
||||||
if stringify {
|
|
||||||
buf = appendStringify(buf, raw)
|
|
||||||
} else {
|
|
||||||
buf = append(buf, raw...)
|
|
||||||
}
|
|
||||||
buf = append(buf, jstr[res.Index+len(res.Raw):]...)
|
|
||||||
return buf, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// parse the path, make sure that it does not contain invalid characters
|
|
||||||
// such as '#', '?', '*'
|
|
||||||
paths := make([]pathResult, 0, 4)
|
|
||||||
r, err := parsePath(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
paths = append(paths, r)
|
|
||||||
for r.more {
|
|
||||||
if r, err = parsePath(r.path); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
paths = append(paths, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
njson, err := appendRawPaths(nil, jstr, paths, raw, stringify, del)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return njson, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set sets a json value for the specified path.
|
// Set sets a json value for the specified path.
|
||||||
// A path is in dot syntax, such as "name.last" or "age".
|
// A path is in dot syntax, such as "name.last" or "age".
|
||||||
// This function expects that the json is well-formed, and does not validate.
|
// This function expects that the json is well-formed, and does not validate.
|
||||||
@ -491,29 +433,6 @@ func Set(json, path string, value interface{}) (string, error) {
|
|||||||
return SetOptions(json, path, value, nil)
|
return SetOptions(json, path, value, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetOptions sets a json value for the specified path with options.
|
|
||||||
// A path is in dot syntax, such as "name.last" or "age".
|
|
||||||
// This function expects that the json is well-formed, and does not validate.
|
|
||||||
// Invalid json will not panic, but it may return back unexpected results.
|
|
||||||
// An error is returned if the path is not valid.
|
|
||||||
func SetOptions(json, path string, value interface{},
|
|
||||||
opts *Options) (string, error) {
|
|
||||||
if opts != nil {
|
|
||||||
if opts.ReplaceInPlace {
|
|
||||||
// it's not safe to replace bytes in-place for strings
|
|
||||||
// copy the Options and set options.ReplaceInPlace to false.
|
|
||||||
nopts := *opts
|
|
||||||
opts = &nopts
|
|
||||||
opts.ReplaceInPlace = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
jsonh := *(*reflect.StringHeader)(unsafe.Pointer(&json))
|
|
||||||
jsonbh := reflect.SliceHeader{Data: jsonh.Data, Len: jsonh.Len}
|
|
||||||
jsonb := *(*[]byte)(unsafe.Pointer(&jsonbh))
|
|
||||||
res, err := SetBytesOptions(jsonb, path, value, opts)
|
|
||||||
return string(res), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetBytes sets a json value for the specified path.
|
// SetBytes sets a json value for the specified path.
|
||||||
// If working with bytes, this method preferred over
|
// If working with bytes, this method preferred over
|
||||||
// Set(string(data), path, value)
|
// Set(string(data), path, value)
|
||||||
@ -521,77 +440,6 @@ func SetBytes(json []byte, path string, value interface{}) ([]byte, error) {
|
|||||||
return SetBytesOptions(json, path, value, nil)
|
return SetBytesOptions(json, path, value, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetBytesOptions sets a json value for the specified path with options.
|
|
||||||
// If working with bytes, this method preferred over
|
|
||||||
// SetOptions(string(data), path, value)
|
|
||||||
func SetBytesOptions(json []byte, path string, value interface{},
|
|
||||||
opts *Options) ([]byte, error) {
|
|
||||||
var optimistic, inplace bool
|
|
||||||
if opts != nil {
|
|
||||||
optimistic = opts.Optimistic
|
|
||||||
inplace = opts.ReplaceInPlace
|
|
||||||
}
|
|
||||||
jstr := *(*string)(unsafe.Pointer(&json))
|
|
||||||
var res []byte
|
|
||||||
var err error
|
|
||||||
switch v := value.(type) {
|
|
||||||
default:
|
|
||||||
b, err := jsongo.Marshal(value)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
raw := *(*string)(unsafe.Pointer(&b))
|
|
||||||
res, err = set(jstr, path, raw, false, false, optimistic, inplace)
|
|
||||||
case dtype:
|
|
||||||
res, err = set(jstr, path, "", false, true, optimistic, inplace)
|
|
||||||
case string:
|
|
||||||
res, err = set(jstr, path, v, true, false, optimistic, inplace)
|
|
||||||
case []byte:
|
|
||||||
raw := *(*string)(unsafe.Pointer(&v))
|
|
||||||
res, err = set(jstr, path, raw, true, false, optimistic, inplace)
|
|
||||||
case bool:
|
|
||||||
if v {
|
|
||||||
res, err = set(jstr, path, "true", false, false, optimistic, inplace)
|
|
||||||
} else {
|
|
||||||
res, err = set(jstr, path, "false", false, false, optimistic, inplace)
|
|
||||||
}
|
|
||||||
case int8:
|
|
||||||
res, err = set(jstr, path, strconv.FormatInt(int64(v), 10),
|
|
||||||
false, false, optimistic, inplace)
|
|
||||||
case int16:
|
|
||||||
res, err = set(jstr, path, strconv.FormatInt(int64(v), 10),
|
|
||||||
false, false, optimistic, inplace)
|
|
||||||
case int32:
|
|
||||||
res, err = set(jstr, path, strconv.FormatInt(int64(v), 10),
|
|
||||||
false, false, optimistic, inplace)
|
|
||||||
case int64:
|
|
||||||
res, err = set(jstr, path, strconv.FormatInt(int64(v), 10),
|
|
||||||
false, false, optimistic, inplace)
|
|
||||||
case uint8:
|
|
||||||
res, err = set(jstr, path, strconv.FormatUint(uint64(v), 10),
|
|
||||||
false, false, optimistic, inplace)
|
|
||||||
case uint16:
|
|
||||||
res, err = set(jstr, path, strconv.FormatUint(uint64(v), 10),
|
|
||||||
false, false, optimistic, inplace)
|
|
||||||
case uint32:
|
|
||||||
res, err = set(jstr, path, strconv.FormatUint(uint64(v), 10),
|
|
||||||
false, false, optimistic, inplace)
|
|
||||||
case uint64:
|
|
||||||
res, err = set(jstr, path, strconv.FormatUint(uint64(v), 10),
|
|
||||||
false, false, optimistic, inplace)
|
|
||||||
case float32:
|
|
||||||
res, err = set(jstr, path, strconv.FormatFloat(float64(v), 'f', -1, 64),
|
|
||||||
false, false, optimistic, inplace)
|
|
||||||
case float64:
|
|
||||||
res, err = set(jstr, path, strconv.FormatFloat(float64(v), 'f', -1, 64),
|
|
||||||
false, false, optimistic, inplace)
|
|
||||||
}
|
|
||||||
if err == errNoChange {
|
|
||||||
return json, nil
|
|
||||||
}
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetRaw sets a raw json value for the specified path.
|
// SetRaw sets a raw json value for the specified path.
|
||||||
// This function works the same as Set except that the value is set as a
|
// This function works the same as Set except that the value is set as a
|
||||||
// raw block of json. This allows for setting premarshalled json objects.
|
// raw block of json. This allows for setting premarshalled json objects.
|
||||||
@ -621,25 +469,6 @@ func SetRawBytes(json []byte, path string, value []byte) ([]byte, error) {
|
|||||||
return SetRawBytesOptions(json, path, value, nil)
|
return SetRawBytesOptions(json, path, value, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetRawBytesOptions sets a raw json value for the specified path with options.
|
|
||||||
// If working with bytes, this method preferred over
|
|
||||||
// SetRawOptions(string(data), path, value, opts)
|
|
||||||
func SetRawBytesOptions(json []byte, path string, value []byte,
|
|
||||||
opts *Options) ([]byte, error) {
|
|
||||||
jstr := *(*string)(unsafe.Pointer(&json))
|
|
||||||
vstr := *(*string)(unsafe.Pointer(&value))
|
|
||||||
var optimistic, inplace bool
|
|
||||||
if opts != nil {
|
|
||||||
optimistic = opts.Optimistic
|
|
||||||
inplace = opts.ReplaceInPlace
|
|
||||||
}
|
|
||||||
res, err := set(jstr, path, vstr, false, false, optimistic, inplace)
|
|
||||||
if err == errNoChange {
|
|
||||||
return json, nil
|
|
||||||
}
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
|
|
||||||
type dtype struct{}
|
type dtype struct{}
|
||||||
|
|
||||||
// Delete deletes a value from json for the specified path.
|
// Delete deletes a value from json for the specified path.
|
||||||
|
196
vendor/github.com/tidwall/sjson/sjson_gae.go
generated
vendored
Normal file
196
vendor/github.com/tidwall/sjson/sjson_gae.go
generated
vendored
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
//+build appengine
|
||||||
|
|
||||||
|
package sjson
|
||||||
|
|
||||||
|
import (
|
||||||
|
jsongo "encoding/json"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
func set(jstr, path, raw string,
|
||||||
|
stringify, del, optimistic, inplace bool) ([]byte, error) {
|
||||||
|
if path == "" {
|
||||||
|
return nil, &errorType{"path cannot be empty"}
|
||||||
|
}
|
||||||
|
if !del && optimistic && isOptimisticPath(path) {
|
||||||
|
res := gjson.Get(jstr, path)
|
||||||
|
if res.Exists() && res.Index > 0 {
|
||||||
|
sz := len(jstr) - len(res.Raw) + len(raw)
|
||||||
|
if stringify {
|
||||||
|
sz += 2
|
||||||
|
}
|
||||||
|
if inplace && sz <= len(jstr) {
|
||||||
|
if !stringify || !mustMarshalString(raw) {
|
||||||
|
// jsonh := *(*reflect.StringHeader)(unsafe.Pointer(&jstr))
|
||||||
|
// jsonbh := reflect.SliceHeader{
|
||||||
|
// Data: jsonh.Data, Len: jsonh.Len, Cap: jsonh.Len}
|
||||||
|
// jbytes := *(*[]byte)(unsafe.Pointer(&jsonbh))
|
||||||
|
jbytes := []byte(jstr)
|
||||||
|
if stringify {
|
||||||
|
jbytes[res.Index] = '"'
|
||||||
|
copy(jbytes[res.Index+1:], []byte(raw))
|
||||||
|
jbytes[res.Index+1+len(raw)] = '"'
|
||||||
|
copy(jbytes[res.Index+1+len(raw)+1:],
|
||||||
|
jbytes[res.Index+len(res.Raw):])
|
||||||
|
} else {
|
||||||
|
copy(jbytes[res.Index:], []byte(raw))
|
||||||
|
copy(jbytes[res.Index+len(raw):],
|
||||||
|
jbytes[res.Index+len(res.Raw):])
|
||||||
|
}
|
||||||
|
return jbytes[:sz], nil
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
buf := make([]byte, 0, sz)
|
||||||
|
buf = append(buf, jstr[:res.Index]...)
|
||||||
|
if stringify {
|
||||||
|
buf = appendStringify(buf, raw)
|
||||||
|
} else {
|
||||||
|
buf = append(buf, raw...)
|
||||||
|
}
|
||||||
|
buf = append(buf, jstr[res.Index+len(res.Raw):]...)
|
||||||
|
return buf, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// parse the path, make sure that it does not contain invalid characters
|
||||||
|
// such as '#', '?', '*'
|
||||||
|
paths := make([]pathResult, 0, 4)
|
||||||
|
r, err := parsePath(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
paths = append(paths, r)
|
||||||
|
for r.more {
|
||||||
|
if r, err = parsePath(r.path); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
paths = append(paths, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
njson, err := appendRawPaths(nil, jstr, paths, raw, stringify, del)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return njson, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOptions sets a json value for the specified path with options.
|
||||||
|
// A path is in dot syntax, such as "name.last" or "age".
|
||||||
|
// This function expects that the json is well-formed, and does not validate.
|
||||||
|
// Invalid json will not panic, but it may return back unexpected results.
|
||||||
|
// An error is returned if the path is not valid.
|
||||||
|
func SetOptions(json, path string, value interface{},
|
||||||
|
opts *Options) (string, error) {
|
||||||
|
if opts != nil {
|
||||||
|
if opts.ReplaceInPlace {
|
||||||
|
// it's not safe to replace bytes in-place for strings
|
||||||
|
// copy the Options and set options.ReplaceInPlace to false.
|
||||||
|
nopts := *opts
|
||||||
|
opts = &nopts
|
||||||
|
opts.ReplaceInPlace = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// jsonh := *(*reflect.StringHeader)(unsafe.Pointer(&json))
|
||||||
|
// jsonbh := reflect.SliceHeader{Data: jsonh.Data, Len: jsonh.Len}
|
||||||
|
// jsonb := *(*[]byte)(unsafe.Pointer(&jsonbh))
|
||||||
|
jsonb := []byte(json)
|
||||||
|
res, err := SetBytesOptions(jsonb, path, value, opts)
|
||||||
|
return string(res), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBytesOptions sets a json value for the specified path with options.
|
||||||
|
// If working with bytes, this method preferred over
|
||||||
|
// SetOptions(string(data), path, value)
|
||||||
|
func SetBytesOptions(json []byte, path string, value interface{},
|
||||||
|
opts *Options) ([]byte, error) {
|
||||||
|
var optimistic, inplace bool
|
||||||
|
if opts != nil {
|
||||||
|
optimistic = opts.Optimistic
|
||||||
|
inplace = opts.ReplaceInPlace
|
||||||
|
}
|
||||||
|
// jstr := *(*string)(unsafe.Pointer(&json))
|
||||||
|
jstr := string(json)
|
||||||
|
var res []byte
|
||||||
|
var err error
|
||||||
|
switch v := value.(type) {
|
||||||
|
default:
|
||||||
|
b, err := jsongo.Marshal(value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// raw := *(*string)(unsafe.Pointer(&b))
|
||||||
|
raw := string(b)
|
||||||
|
res, err = set(jstr, path, raw, false, false, optimistic, inplace)
|
||||||
|
case dtype:
|
||||||
|
res, err = set(jstr, path, "", false, true, optimistic, inplace)
|
||||||
|
case string:
|
||||||
|
res, err = set(jstr, path, v, true, false, optimistic, inplace)
|
||||||
|
case []byte:
|
||||||
|
// raw := *(*string)(unsafe.Pointer(&v))
|
||||||
|
raw := string(v)
|
||||||
|
res, err = set(jstr, path, raw, true, false, optimistic, inplace)
|
||||||
|
case bool:
|
||||||
|
if v {
|
||||||
|
res, err = set(jstr, path, "true", false, false, optimistic, inplace)
|
||||||
|
} else {
|
||||||
|
res, err = set(jstr, path, "false", false, false, optimistic, inplace)
|
||||||
|
}
|
||||||
|
case int8:
|
||||||
|
res, err = set(jstr, path, strconv.FormatInt(int64(v), 10),
|
||||||
|
false, false, optimistic, inplace)
|
||||||
|
case int16:
|
||||||
|
res, err = set(jstr, path, strconv.FormatInt(int64(v), 10),
|
||||||
|
false, false, optimistic, inplace)
|
||||||
|
case int32:
|
||||||
|
res, err = set(jstr, path, strconv.FormatInt(int64(v), 10),
|
||||||
|
false, false, optimistic, inplace)
|
||||||
|
case int64:
|
||||||
|
res, err = set(jstr, path, strconv.FormatInt(int64(v), 10),
|
||||||
|
false, false, optimistic, inplace)
|
||||||
|
case uint8:
|
||||||
|
res, err = set(jstr, path, strconv.FormatUint(uint64(v), 10),
|
||||||
|
false, false, optimistic, inplace)
|
||||||
|
case uint16:
|
||||||
|
res, err = set(jstr, path, strconv.FormatUint(uint64(v), 10),
|
||||||
|
false, false, optimistic, inplace)
|
||||||
|
case uint32:
|
||||||
|
res, err = set(jstr, path, strconv.FormatUint(uint64(v), 10),
|
||||||
|
false, false, optimistic, inplace)
|
||||||
|
case uint64:
|
||||||
|
res, err = set(jstr, path, strconv.FormatUint(uint64(v), 10),
|
||||||
|
false, false, optimistic, inplace)
|
||||||
|
case float32:
|
||||||
|
res, err = set(jstr, path, strconv.FormatFloat(float64(v), 'f', -1, 64),
|
||||||
|
false, false, optimistic, inplace)
|
||||||
|
case float64:
|
||||||
|
res, err = set(jstr, path, strconv.FormatFloat(float64(v), 'f', -1, 64),
|
||||||
|
false, false, optimistic, inplace)
|
||||||
|
}
|
||||||
|
if err == errNoChange {
|
||||||
|
return json, nil
|
||||||
|
}
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRawBytesOptions sets a raw json value for the specified path with options.
|
||||||
|
// If working with bytes, this method preferred over
|
||||||
|
// SetRawOptions(string(data), path, value, opts)
|
||||||
|
func SetRawBytesOptions(json []byte, path string, value []byte,
|
||||||
|
opts *Options) ([]byte, error) {
|
||||||
|
// jstr := *(*string)(unsafe.Pointer(&json))
|
||||||
|
// vstr := *(*string)(unsafe.Pointer(&value))
|
||||||
|
jstr := string(json)
|
||||||
|
vstr := string(value)
|
||||||
|
var optimistic, inplace bool
|
||||||
|
if opts != nil {
|
||||||
|
optimistic = opts.Optimistic
|
||||||
|
inplace = opts.ReplaceInPlace
|
||||||
|
}
|
||||||
|
res, err := set(jstr, path, vstr, false, false, optimistic, inplace)
|
||||||
|
if err == errNoChange {
|
||||||
|
return json, nil
|
||||||
|
}
|
||||||
|
return res, err
|
||||||
|
}
|
191
vendor/github.com/tidwall/sjson/sjson_ngae.go
generated
vendored
Normal file
191
vendor/github.com/tidwall/sjson/sjson_ngae.go
generated
vendored
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
//+build !appengine
|
||||||
|
|
||||||
|
package sjson
|
||||||
|
|
||||||
|
import (
|
||||||
|
jsongo "encoding/json"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
func set(jstr, path, raw string,
|
||||||
|
stringify, del, optimistic, inplace bool) ([]byte, error) {
|
||||||
|
if path == "" {
|
||||||
|
return nil, &errorType{"path cannot be empty"}
|
||||||
|
}
|
||||||
|
if !del && optimistic && isOptimisticPath(path) {
|
||||||
|
res := gjson.Get(jstr, path)
|
||||||
|
if res.Exists() && res.Index > 0 {
|
||||||
|
sz := len(jstr) - len(res.Raw) + len(raw)
|
||||||
|
if stringify {
|
||||||
|
sz += 2
|
||||||
|
}
|
||||||
|
if inplace && sz <= len(jstr) {
|
||||||
|
if !stringify || !mustMarshalString(raw) {
|
||||||
|
jsonh := *(*reflect.StringHeader)(unsafe.Pointer(&jstr))
|
||||||
|
jsonbh := reflect.SliceHeader{
|
||||||
|
Data: jsonh.Data, Len: jsonh.Len, Cap: jsonh.Len}
|
||||||
|
jbytes := *(*[]byte)(unsafe.Pointer(&jsonbh))
|
||||||
|
if stringify {
|
||||||
|
jbytes[res.Index] = '"'
|
||||||
|
copy(jbytes[res.Index+1:], []byte(raw))
|
||||||
|
jbytes[res.Index+1+len(raw)] = '"'
|
||||||
|
copy(jbytes[res.Index+1+len(raw)+1:],
|
||||||
|
jbytes[res.Index+len(res.Raw):])
|
||||||
|
} else {
|
||||||
|
copy(jbytes[res.Index:], []byte(raw))
|
||||||
|
copy(jbytes[res.Index+len(raw):],
|
||||||
|
jbytes[res.Index+len(res.Raw):])
|
||||||
|
}
|
||||||
|
return jbytes[:sz], nil
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
buf := make([]byte, 0, sz)
|
||||||
|
buf = append(buf, jstr[:res.Index]...)
|
||||||
|
if stringify {
|
||||||
|
buf = appendStringify(buf, raw)
|
||||||
|
} else {
|
||||||
|
buf = append(buf, raw...)
|
||||||
|
}
|
||||||
|
buf = append(buf, jstr[res.Index+len(res.Raw):]...)
|
||||||
|
return buf, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// parse the path, make sure that it does not contain invalid characters
|
||||||
|
// such as '#', '?', '*'
|
||||||
|
paths := make([]pathResult, 0, 4)
|
||||||
|
r, err := parsePath(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
paths = append(paths, r)
|
||||||
|
for r.more {
|
||||||
|
if r, err = parsePath(r.path); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
paths = append(paths, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
njson, err := appendRawPaths(nil, jstr, paths, raw, stringify, del)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return njson, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOptions sets a json value for the specified path with options.
|
||||||
|
// A path is in dot syntax, such as "name.last" or "age".
|
||||||
|
// This function expects that the json is well-formed, and does not validate.
|
||||||
|
// Invalid json will not panic, but it may return back unexpected results.
|
||||||
|
// An error is returned if the path is not valid.
|
||||||
|
func SetOptions(json, path string, value interface{},
|
||||||
|
opts *Options) (string, error) {
|
||||||
|
if opts != nil {
|
||||||
|
if opts.ReplaceInPlace {
|
||||||
|
// it's not safe to replace bytes in-place for strings
|
||||||
|
// copy the Options and set options.ReplaceInPlace to false.
|
||||||
|
nopts := *opts
|
||||||
|
opts = &nopts
|
||||||
|
opts.ReplaceInPlace = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jsonh := *(*reflect.StringHeader)(unsafe.Pointer(&json))
|
||||||
|
jsonbh := reflect.SliceHeader{Data: jsonh.Data, Len: jsonh.Len}
|
||||||
|
jsonb := *(*[]byte)(unsafe.Pointer(&jsonbh))
|
||||||
|
res, err := SetBytesOptions(jsonb, path, value, opts)
|
||||||
|
return string(res), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBytesOptions sets a json value for the specified path with options.
|
||||||
|
// If working with bytes, this method preferred over
|
||||||
|
// SetOptions(string(data), path, value)
|
||||||
|
func SetBytesOptions(json []byte, path string, value interface{},
|
||||||
|
opts *Options) ([]byte, error) {
|
||||||
|
var optimistic, inplace bool
|
||||||
|
if opts != nil {
|
||||||
|
optimistic = opts.Optimistic
|
||||||
|
inplace = opts.ReplaceInPlace
|
||||||
|
}
|
||||||
|
jstr := *(*string)(unsafe.Pointer(&json))
|
||||||
|
var res []byte
|
||||||
|
var err error
|
||||||
|
switch v := value.(type) {
|
||||||
|
default:
|
||||||
|
b, err := jsongo.Marshal(value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
raw := *(*string)(unsafe.Pointer(&b))
|
||||||
|
res, err = set(jstr, path, raw, false, false, optimistic, inplace)
|
||||||
|
case dtype:
|
||||||
|
res, err = set(jstr, path, "", false, true, optimistic, inplace)
|
||||||
|
case string:
|
||||||
|
res, err = set(jstr, path, v, true, false, optimistic, inplace)
|
||||||
|
case []byte:
|
||||||
|
raw := *(*string)(unsafe.Pointer(&v))
|
||||||
|
res, err = set(jstr, path, raw, true, false, optimistic, inplace)
|
||||||
|
case bool:
|
||||||
|
if v {
|
||||||
|
res, err = set(jstr, path, "true", false, false, optimistic, inplace)
|
||||||
|
} else {
|
||||||
|
res, err = set(jstr, path, "false", false, false, optimistic, inplace)
|
||||||
|
}
|
||||||
|
case int8:
|
||||||
|
res, err = set(jstr, path, strconv.FormatInt(int64(v), 10),
|
||||||
|
false, false, optimistic, inplace)
|
||||||
|
case int16:
|
||||||
|
res, err = set(jstr, path, strconv.FormatInt(int64(v), 10),
|
||||||
|
false, false, optimistic, inplace)
|
||||||
|
case int32:
|
||||||
|
res, err = set(jstr, path, strconv.FormatInt(int64(v), 10),
|
||||||
|
false, false, optimistic, inplace)
|
||||||
|
case int64:
|
||||||
|
res, err = set(jstr, path, strconv.FormatInt(int64(v), 10),
|
||||||
|
false, false, optimistic, inplace)
|
||||||
|
case uint8:
|
||||||
|
res, err = set(jstr, path, strconv.FormatUint(uint64(v), 10),
|
||||||
|
false, false, optimistic, inplace)
|
||||||
|
case uint16:
|
||||||
|
res, err = set(jstr, path, strconv.FormatUint(uint64(v), 10),
|
||||||
|
false, false, optimistic, inplace)
|
||||||
|
case uint32:
|
||||||
|
res, err = set(jstr, path, strconv.FormatUint(uint64(v), 10),
|
||||||
|
false, false, optimistic, inplace)
|
||||||
|
case uint64:
|
||||||
|
res, err = set(jstr, path, strconv.FormatUint(uint64(v), 10),
|
||||||
|
false, false, optimistic, inplace)
|
||||||
|
case float32:
|
||||||
|
res, err = set(jstr, path, strconv.FormatFloat(float64(v), 'f', -1, 64),
|
||||||
|
false, false, optimistic, inplace)
|
||||||
|
case float64:
|
||||||
|
res, err = set(jstr, path, strconv.FormatFloat(float64(v), 'f', -1, 64),
|
||||||
|
false, false, optimistic, inplace)
|
||||||
|
}
|
||||||
|
if err == errNoChange {
|
||||||
|
return json, nil
|
||||||
|
}
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRawBytesOptions sets a raw json value for the specified path with options.
|
||||||
|
// If working with bytes, this method preferred over
|
||||||
|
// SetRawOptions(string(data), path, value, opts)
|
||||||
|
func SetRawBytesOptions(json []byte, path string, value []byte,
|
||||||
|
opts *Options) ([]byte, error) {
|
||||||
|
jstr := *(*string)(unsafe.Pointer(&json))
|
||||||
|
vstr := *(*string)(unsafe.Pointer(&value))
|
||||||
|
var optimistic, inplace bool
|
||||||
|
if opts != nil {
|
||||||
|
optimistic = opts.Optimistic
|
||||||
|
inplace = opts.ReplaceInPlace
|
||||||
|
}
|
||||||
|
res, err := set(jstr, path, vstr, false, false, optimistic, inplace)
|
||||||
|
if err == errNoChange {
|
||||||
|
return json, nil
|
||||||
|
}
|
||||||
|
return res, err
|
||||||
|
}
|
1186
vendor/github.com/tidwall/sjson/sjson_test.go
generated
vendored
1186
vendor/github.com/tidwall/sjson/sjson_test.go
generated
vendored
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user