Update gjson/sjson

This commit is contained in:
tidwall 2018-10-18 06:30:41 -07:00
parent cc75cf22a8
commit 3e41a2ecce
11 changed files with 1206 additions and 1878 deletions

12
Gopkg.lock generated
View File

@ -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"

View File

@ -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).*

View File

@ -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
View 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
View 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
}
}
}

View File

@ -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)
}
}

View File

@ -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)

View File

@ -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
View 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
View 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
}

File diff suppressed because it is too large Load Diff