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"
[[projects]]
digest = "1:211773b67c5594aa92b1e8389c59558fa4927614507ea38237265e00c0ba6b81"
digest = "1:3ddca2bd5496c6922a2a9e636530e178a43c2a534ea6634211acdc7d10222794"
name = "github.com/tidwall/gjson"
packages = ["."]
pruneopts = ""
revision = "5a69e67cfd8f6f9b0044ed49f5079d0eeed28653"
version = "v1.0.1"
revision = "1e3f6aeaa5bad08d777ea7807b279a07885dd8b2"
version = "v1.1.3"
[[projects]]
branch = "master"
@ -310,12 +310,12 @@
revision = "d4a8a3d30d5729f85edfba1745241f3a621d0359"
[[projects]]
digest = "1:ed6a1c415a0bd35c9c18eec74bfd460a57ba21fb3bc0da629afc275096edffa4"
digest = "1:ca969d3e75ed5b3003f4f5864bb5c13d99471ef57f9049bf78562d7ee1ac019c"
name = "github.com/tidwall/sjson"
packages = ["."]
pruneopts = ""
revision = "6a22caf2fd45d5e2119bfc3717e984f15a7eb7ee"
version = "v1.0.0"
revision = "48d34adceb39a5bd6ed7c12f38c78cd425436442"
version = "v1.0.2"
[[projects]]
branch = "master"

View File

@ -5,15 +5,17 @@
<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://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 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.
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
===============
@ -29,7 +31,7 @@ $ go get -u github.com/tidwall/gjson
This will retrieve the library.
## Get a value
Get searches json for the specified path. A path is in dot syntax, such as "name.last" or "age". 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
package main
@ -95,6 +97,36 @@ friends.#[age>45]#.last >> ["Craig","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
GJSON supports the json types `string`, `number`, `bool`, and `null`.
@ -152,6 +184,15 @@ array >> []interface{}
object >> map[string]interface{}
```
### 64-bit integers
The `result.Int()` and `result.Uint()` calls are capable of reading all 64 bits, allowing for large JSON integers.
```go
result.Int() int64 // -9223372036854775808 to 9223372036854775807
result.Uint() int64 // 0 to 18446744073709551615
```
## Get nested array values
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.
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.
The `Get*` and `Parse*` functions expects that the json is well-formed. Bad json will not panic, but it may return back unexpected results.
If you are consuming JSON from an unpredictable source then you may want to validate prior to using GJSON.
```go
package main
import (
"fmt"
"github.com/tidwall/gjson"
)
type Animal struct {
Type string `json:"type"`
Sound string `json:"sound"`
Age int `json:"age"`
if !gjson.Valid(json) {
return errors.New("invalid json")
}
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
value := gjson.Get(json, "name.last")
```
## 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
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
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
BenchmarkGJSONUnmarshalMap-8 900000 4154 ns/op 1920 B/op 26 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
BenchmarkFFJSONLexer-8 1500000 3111 ns/op 896 B/op 8 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
```
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
@ -395,21 +390,6 @@ widget.image.hOffset
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).*

View File

@ -13,7 +13,6 @@ import (
"time"
"unicode/utf16"
"unicode/utf8"
"unsafe"
"github.com/tidwall/match"
)
@ -78,7 +77,20 @@ func (t Result) String() string {
case False:
return "false"
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:
return t.Str
case JSON:
@ -96,7 +108,7 @@ func (t Result) Bool() bool {
case True:
return true
case String:
return t.Str != "" && t.Str != "0"
return t.Str != "" && t.Str != "0" && t.Str != "false"
case Number:
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 is not a JSON array, the return value will be an array containing one result.
func (t Result) Array() []Result {
if !t.Exists() {
return nil
if t.Type == Null {
return []Result{}
}
if t.Type != JSON {
return []Result{t}
@ -192,7 +204,7 @@ func (t Result) IsObject() bool {
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 {
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] == '-' {
value.Type = Number
value.Raw, value.Num = tonum(json[i:])
value.Str = ""
} else {
continue
}
case '{', '[':
value.Type = JSON
value.Raw = squash(json[i:])
value.Str, value.Num = "", 0
case 'n':
value.Type = Null
value.Raw = tolit(json[i:])
value.Str, value.Num = "", 0
case 't':
value.Type = True
value.Raw = tolit(json[i:])
value.Str, value.Num = "", 0
case 'f':
value.Type = False
value.Raw = tolit(json[i:])
value.Str, value.Num = "", 0
case '"':
value.Type = String
value.Raw, value.Str = tostr(json[i:])
value.Num = 0
}
i += len(value.Raw) - 1
@ -371,9 +389,13 @@ func (t Result) arrayOrMap(vc byte, valueize bool) (r arrayOrMapResult) {
key = value
} else {
if valueize {
r.oi[key.Str] = value.Value()
if _, ok := r.oi[key.Str]; !ok {
r.oi[key.Str] = value.Value()
}
} else {
r.o[key.Str] = value
if _, ok := r.o[key.Str]; !ok {
r.o[key.Str] = value
}
}
}
count++
@ -390,6 +412,11 @@ end:
}
// Parse parses the json and returns a result.
//
// This function expects that the json is well-formed, and does not validate.
// Invalid json will not panic, but it may return back unexpected results.
// If you are consuming JSON from an unpredictable source then you may want to
// use the Valid function first.
func Parse(json string) Result {
var value Result
for i := 0; i < len(json); i++ {
@ -511,7 +538,7 @@ func tonum(json string) (raw string, num float64) {
func tolit(json string) (raw string) {
for i := 1; i < len(json); i++ {
if json[i] <= 'a' || json[i] >= 'z' {
if json[i] < 'a' || json[i] > 'z' {
return json[:i]
}
}
@ -578,6 +605,8 @@ func (t Result) Exists() bool {
// Number, for JSON numbers
// string, for JSON string literals
// nil, for JSON null
// map[string]interface{}, for JSON objects
// []interface{}, for JSON arrays
//
func (t Result) Value() interface{} {
if t.Type == String {
@ -1077,7 +1106,7 @@ func queryMatches(rp *arrayPathResult, value Result) bool {
case "=":
return value.Num == rpvn
case "!=":
return value.Num == rpvn
return value.Num != rpvn
case "<":
return value.Num < rpvn
case "<=":
@ -1128,7 +1157,7 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
partidx = int(n)
}
}
for i < len(c.json) {
for i < len(c.json)+1 {
if !rp.arrch {
pmatch = partidx == h
hit = pmatch && !rp.more
@ -1137,8 +1166,16 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
if rp.alogok {
alog = append(alog, i)
}
for ; i < len(c.json); i++ {
switch c.json[i] {
for ; ; i++ {
var ch byte
if i > len(c.json) {
break
} else if i == len(c.json) {
ch = ']'
} else {
ch = c.json[i]
}
switch ch {
default:
continue
case '"':
@ -1252,14 +1289,18 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
if rp.alogok {
var jsons = make([]byte, 0, 64)
jsons = append(jsons, '[')
for j, k := 0, 0; j < len(alog); j++ {
res := Get(c.json[alog[j]:], rp.alogkey)
if res.Exists() {
if k > 0 {
jsons = append(jsons, ',')
_, res, ok := parseAny(c.json, alog[j], true)
if ok {
res := res.Get(rp.alogkey)
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, ']')
@ -1270,7 +1311,7 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
if rp.alogok {
break
}
c.value.Raw = val
c.value.Raw = ""
c.value.Type = Number
c.value.Num = float64(h - 1)
c.calcd = true
@ -1290,16 +1331,32 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
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 {
json string
value Result
calcd bool
lines bool
}
// 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, 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.
//
// A path is a series of keys searated by a dot.
@ -1326,79 +1383,38 @@ type parseContext struct {
// "c?ildren.0" >> "Sara"
// "friends.#.first" >> ["James","Roger"]
//
// This function expects that the json is well-formed, and does not validate.
// Invalid json will not panic, but it may return back unexpected results.
// If you are consuming JSON from an unpredictable source then you may want to
// use the Valid function first.
func Get(json, path string) Result {
var i int
var c = &parseContext{json: json}
for ; i < len(c.json); i++ {
if c.json[i] == '{' {
i++
parseObject(c, i, path)
break
}
if c.json[i] == '[' {
i++
parseArray(c, i, path)
break
}
}
if 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]
if len(path) >= 2 && path[0] == '.' && path[1] == '.' {
c.lines = true
parseArray(c, 0, path[2:])
} 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)))
for ; i < len(c.json); i++ {
if c.json[i] == '{' {
i++
parseObject(c, i, path)
break
}
if c.json[i] == '[' {
i++
parseArray(c, i, path)
break
}
}
}
return result
fillIndex(json, c)
return c.value
}
// GetBytes searches json for the specified path.
// If working with bytes, this method preferred over Get(string(data), path)
func GetBytes(json []byte, path string) Result {
var result Result
if json != nil {
// unsafe cast to string
result = Get(*(*string)(unsafe.Pointer(&json)), path)
result = fromBytesGet(result)
}
return result
return getBytes(json, path)
}
// runeit returns the rune from the the \uXXXX
@ -1595,405 +1611,22 @@ var ( // used for testing
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.
// The return value is a Result array where the number of items
// will be equal to the number of input paths.
func GetMany(json string, paths ...string) []Result {
if len(paths) < 4 {
if testWatchForFallback {
testLastWasFallback = false
}
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])}
}
func GetMany(json string, path ...string) []Result {
res := make([]Result, len(path))
for i, path := range path {
res[i] = Get(json, path)
}
var results []Result
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
return res
}
// GetManyBytes searches json for the specified path.
// If working with bytes, this method preferred over
// GetMany(string(data), paths...)
func GetManyBytes(json []byte, paths ...string) []Result {
if json == nil {
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
// GetManyBytes searches json for the multiple paths.
// The return value is a Result array where the number of items
// will be equal to the number of input paths.
func GetManyBytes(json []byte, path ...string) []Result {
return GetMany(string(json), path...)
}
var fieldsmu sync.RWMutex
@ -2099,6 +1732,8 @@ var validate uintptr = 1
// UnmarshalValidationEnabled provides the option to disable JSON validation
// during the Unmarshal routine. Validation is enabled by default.
//
// Deprecated: Use encoder/json.Unmarshal instead
func UnmarshalValidationEnabled(enabled bool) {
if enabled {
atomic.StoreUintptr(&validate, 1)
@ -2113,6 +1748,8 @@ func UnmarshalValidationEnabled(enabled bool) {
// 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.
//
// Deprecated: Use encoder/json.Unmarshal instead
func Unmarshal(data []byte, v interface{}) error {
if atomic.LoadUintptr(&validate) == 1 {
_, ok := validpayload(data, 0)
@ -2200,8 +1837,14 @@ func validobject(data []byte, i int) (outi int, ok bool) {
if data[i] == '}' {
return i + 1, true
}
i++
for ; i < len(data); i++ {
if data[i] == '"' {
switch data[i] {
default:
return i, false
case ' ', '\t', '\n', '\r':
continue
case '"':
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.
//
// if !gjson.Valid(json) {
// return errors.New("invalid json")
// }
// value := gjson.Get(json, "name.last")
//
func Valid(json string) bool {
_, ok := validpayload([]byte(json), 0)
return ok
}
// ValidBytes returns true if the input is valid json.
//
// if !gjson.Valid(json) {
// return errors.New("invalid json")
// }
// value := gjson.Get(json, "name.last")
//
// If working with bytes, this method preferred over Valid(string(data))
//
func ValidBytes(json []byte) bool {
_, ok := validpayload(json, 0)
return ok
}
func parseUint(s string) (n uint64, ok bool) {
var i int
if i == len(s) {

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"
"math/rand"
"reflect"
"strconv"
"strings"
"testing"
"time"
@ -147,7 +148,7 @@ func TestTimeResult(t *testing.T) {
func TestParseAny(t *testing.T) {
assert(t, Parse("100").Float() == 100)
assert(t, Parse("true").Bool())
assert(t, Parse("valse").Bool() == false)
assert(t, Parse("false").Bool() == false)
}
func TestManyVariousPathCounts(t *testing.T) {
@ -478,7 +479,8 @@ func TestBasic4(t *testing.T) {
}
token = get(basicJSON, "arr.#")
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")
if token.String() != "world" {
@ -969,80 +971,82 @@ func TestUnmarshal(t *testing.T) {
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)
if ok != expect {
panic("mismatch")
t.Fatal("mismatch")
}
}
func TestValidBasic(t *testing.T) {
testvalid("0", true)
testvalid("00", false)
testvalid("-00", false)
testvalid("-.", false)
testvalid("0.0", true)
testvalid("10.0", true)
testvalid("10e1", true)
testvalid("10EE", false)
testvalid("10E-", false)
testvalid("10E+", false)
testvalid("10E123", true)
testvalid("10E-123", true)
testvalid("10E-0123", true)
testvalid("", false)
testvalid(" ", false)
testvalid("{}", true)
testvalid("{", false)
testvalid("-", false)
testvalid("-1", true)
testvalid("-1.", false)
testvalid("-1.0", true)
testvalid(" -1.0", true)
testvalid(" -1.0 ", true)
testvalid("-1.0 ", true)
testvalid("-1.0 i", false)
testvalid("-1.0 i", false)
testvalid("true", true)
testvalid(" true", true)
testvalid(" true ", true)
testvalid(" True ", false)
testvalid(" tru", false)
testvalid("false", true)
testvalid(" false", true)
testvalid(" false ", true)
testvalid(" False ", false)
testvalid(" fals", false)
testvalid("null", true)
testvalid(" null", true)
testvalid(" null ", true)
testvalid(" Null ", false)
testvalid(" nul", false)
testvalid(" []", true)
testvalid(" [true]", true)
testvalid(" [ true, null ]", true)
testvalid(" [ true,]", false)
testvalid(`{"hello":"world"}`, true)
testvalid(`{ "hello": "world" }`, true)
testvalid(`{ "hello": "world", }`, false)
testvalid(`{"a":"b",}`, false)
testvalid(`{"a":"b","a"}`, false)
testvalid(`{"a":"b","a":}`, false)
testvalid(`{"a":"b","a":1}`, true)
testvalid(`{"a":"b","a": 1, "c":{"hi":"there"} }`, true)
testvalid(`{"a":"b","a": 1, "c":{"hi":"there", "easy":["going",{"mixed":"bag"}]} }`, true)
testvalid(`""`, true)
testvalid(`"`, false)
testvalid(`"\n"`, true)
testvalid(`"\"`, false)
testvalid(`"\\"`, true)
testvalid(`"a\\b"`, true)
testvalid(`"a\\b\\\"a"`, true)
testvalid(`"a\\b\\\uFFAAa"`, true)
testvalid(`"a\\b\\\uFFAZa"`, false)
testvalid(`"a\\b\\\uFFA"`, false)
testvalid(string(complicatedJSON), true)
testvalid(string(exampleJSON), true)
testvalid(t, "0", true)
testvalid(t, "00", false)
testvalid(t, "-00", false)
testvalid(t, "-.", false)
testvalid(t, "0.0", true)
testvalid(t, "10.0", true)
testvalid(t, "10e1", true)
testvalid(t, "10EE", false)
testvalid(t, "10E-", false)
testvalid(t, "10E+", false)
testvalid(t, "10E123", true)
testvalid(t, "10E-123", true)
testvalid(t, "10E-0123", true)
testvalid(t, "", false)
testvalid(t, " ", false)
testvalid(t, "{}", true)
testvalid(t, "{", false)
testvalid(t, "-", false)
testvalid(t, "-1", true)
testvalid(t, "-1.", false)
testvalid(t, "-1.0", true)
testvalid(t, " -1.0", true)
testvalid(t, " -1.0 ", true)
testvalid(t, "-1.0 ", true)
testvalid(t, "-1.0 i", false)
testvalid(t, "-1.0 i", false)
testvalid(t, "true", true)
testvalid(t, " true", true)
testvalid(t, " true ", true)
testvalid(t, " True ", false)
testvalid(t, " tru", false)
testvalid(t, "false", true)
testvalid(t, " false", true)
testvalid(t, " false ", true)
testvalid(t, " False ", false)
testvalid(t, " fals", false)
testvalid(t, "null", true)
testvalid(t, " null", true)
testvalid(t, " null ", true)
testvalid(t, " Null ", false)
testvalid(t, " nul", false)
testvalid(t, " []", true)
testvalid(t, " [true]", true)
testvalid(t, " [ true, null ]", true)
testvalid(t, " [ true,]", false)
testvalid(t, `{"hello":"world"}`, true)
testvalid(t, `{ "hello": "world" }`, true)
testvalid(t, `{ "hello": "world", }`, false)
testvalid(t, `{"a":"b",}`, false)
testvalid(t, `{"a":"b","a"}`, false)
testvalid(t, `{"a":"b","a":}`, false)
testvalid(t, `{"a":"b","a":1}`, true)
testvalid(t, `{"a":"b",2"1":2}`, false)
testvalid(t, `{"a":"b","a": 1, "c":{"hi":"there"} }`, true)
testvalid(t, `{"a":"b","a": 1, "c":{"hi":"there", "easy":["going",{"mixed":"bag"}]} }`, true)
testvalid(t, `""`, true)
testvalid(t, `"`, false)
testvalid(t, `"\n"`, true)
testvalid(t, `"\"`, false)
testvalid(t, `"\\"`, true)
testvalid(t, `"a\\b"`, true)
testvalid(t, `"a\\b\\\"a"`, true)
testvalid(t, `"a\\b\\\uFFAAa"`, true)
testvalid(t, `"a\\b\\\uFFAZa"`, false)
testvalid(t, `"a\\b\\\uFFA"`, false)
testvalid(t, string(complicatedJSON), true)
testvalid(t, string(exampleJSON), true)
}
var jsonchars = []string{"{", "[", ",", ":", "}", "]", "1", "0", "true", "false", "null", `""`, `"\""`, `"a"`}
@ -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>
</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.
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
===============
@ -59,7 +59,7 @@ Path syntax
-----------
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
{
@ -268,7 +268,7 @@ widget.image.hOffset
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
Josh Baker [@tidwall](http://twitter.com/tidwall)

View File

@ -3,9 +3,7 @@ package sjson
import (
jsongo "encoding/json"
"reflect"
"strconv"
"unsafe"
"github.com/tidwall/gjson"
)
@ -36,6 +34,7 @@ type Options struct {
type pathResult struct {
part string // current key part
gpart string // gjson get part
path string // remaining path
force bool // force a string key
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++ {
if path[i] == '.' {
r.part = path[:i]
r.gpart = path[:i]
r.path = path[i+1:]
r.more = true
return r, nil
@ -63,19 +63,24 @@ func parsePath(path string) (pathResult, error) {
// go into escape mode. this is a slower path that
// strips off the escape character from the part.
epart := []byte(path[:i])
gpart := []byte(path[:i+1])
i++
if i < len(path) {
epart = append(epart, path[i])
gpart = append(gpart, path[i])
i++
for ; i < len(path); i++ {
if path[i] == '\\' {
gpart = append(gpart, '\\')
i++
if i < len(path) {
epart = append(epart, path[i])
gpart = append(gpart, path[i])
}
continue
} else if path[i] == '.' {
r.part = string(epart)
r.gpart = string(gpart)
r.path = path[i+1:]
r.more = true
return r, nil
@ -87,20 +92,23 @@ func parsePath(path string) (pathResult, error) {
"array access character not allowed in path"}
}
epart = append(epart, path[i])
gpart = append(gpart, path[i])
}
}
// append the last part
r.part = string(epart)
r.gpart = string(gpart)
return r, nil
}
}
r.part = path
r.gpart = path
return r, nil
}
func mustMarshalString(s string) bool {
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
}
}
@ -208,7 +216,7 @@ loop:
for ; i >= 0; i-- {
if buf[i] == '"' {
i--
if i >= 0 && i == '\\' {
if i >= 0 && buf[i] == '\\' {
i--
continue
}
@ -249,7 +257,7 @@ func appendRawPaths(buf []byte, jstr string, paths []pathResult, raw string,
}
}
if !found {
res = gjson.Get(jstr, paths[0].part)
res = gjson.Get(jstr, paths[0].gpart)
}
if res.Index > 0 {
if len(paths) > 1 {
@ -400,72 +408,6 @@ func isOptimisticPath(path string) bool {
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.
// 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.
@ -491,29 +433,6 @@ func Set(json, path string, value interface{}) (string, error) {
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.
// If working with bytes, this method preferred over
// 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)
}
// 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.
// 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.
@ -621,25 +469,6 @@ func SetRawBytes(json []byte, path string, value []byte) ([]byte, error) {
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{}
// 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