Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e3f6aeaa5 | ||
|
|
f92dbfc6b2 | ||
|
|
ba784d767a | ||
|
|
f123b34087 | ||
|
|
afaeb95620 | ||
|
|
3cd3a11923 | ||
|
|
3a977634eb | ||
|
|
9ed3f8e1a5 | ||
|
|
93d61e6369 | ||
|
|
01f00f1296 | ||
|
|
9fa9086994 | ||
|
|
aff9dcea3c | ||
|
|
c2e370e1b3 | ||
|
|
df0b0cce42 | ||
|
|
46c712f1ce | ||
|
|
54d114d487 | ||
|
|
6d43584b26 | ||
|
|
4b8623b9d6 | ||
|
|
749b396bb9 | ||
|
|
077965694f | ||
|
|
b9953e2865 | ||
|
|
a2f35b522e | ||
|
|
5fe9078c47 | ||
|
|
bff3f07fe7 | ||
|
|
87033efcae | ||
|
|
5cd723d566 | ||
|
|
62ee2064df | ||
|
|
09641abb33 |
62
README.md
62
README.md
@@ -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,6 +275,19 @@ if gjson.Get(json, "name.last").Exists() {
|
||||
}
|
||||
```
|
||||
|
||||
## Validate JSON
|
||||
|
||||
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
|
||||
if !gjson.Valid(json) {
|
||||
return errors.New("invalid json")
|
||||
}
|
||||
value := gjson.Get(json, "name.last")
|
||||
```
|
||||
|
||||
## Unmarshal to a map
|
||||
|
||||
To unmarshal to a `map[string]interface{}`:
|
||||
|
||||
212
gjson.go
212
gjson.go
@@ -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
|
||||
}
|
||||
@@ -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++ {
|
||||
@@ -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
|
||||
@@ -1610,11 +1626,7 @@ func GetMany(json string, path ...string) []Result {
|
||||
// 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 {
|
||||
res := make([]Result, len(path))
|
||||
for i, path := range path {
|
||||
res[i] = GetBytes(json, path)
|
||||
}
|
||||
return res
|
||||
return GetMany(string(json), path...)
|
||||
}
|
||||
|
||||
var fieldsmu sync.RWMutex
|
||||
@@ -1825,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
|
||||
}
|
||||
}
|
||||
@@ -2007,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
gjson_gae.go
Normal file
10
gjson_gae.go
Normal file
@@ -0,0 +1,10 @@
|
||||
//+build appengine
|
||||
|
||||
package gjson
|
||||
|
||||
func getBytes(json []byte, path string) Result {
|
||||
return Get(string(json), path)
|
||||
}
|
||||
func fillIndex(json string, c *parseContext) {
|
||||
// noop. Use zero for the Index value.
|
||||
}
|
||||
73
gjson_ngae.go
Normal file
73
gjson_ngae.go
Normal file
@@ -0,0 +1,73 @@
|
||||
//+build !appengine
|
||||
|
||||
package gjson
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// getBytes casts the input json bytes to a string and safely returns the
|
||||
// results as uniquely allocated data. This operation is intended to minimize
|
||||
// copies and allocations for the large json string->[]byte.
|
||||
func getBytes(json []byte, path string) Result {
|
||||
var result Result
|
||||
if json != nil {
|
||||
// unsafe cast to string
|
||||
result = Get(*(*string)(unsafe.Pointer(&json)), path)
|
||||
result = fromBytesGet(result)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func fromBytesGet(result Result) Result {
|
||||
// safely get the string headers
|
||||
rawhi := *(*reflect.StringHeader)(unsafe.Pointer(&result.Raw))
|
||||
strhi := *(*reflect.StringHeader)(unsafe.Pointer(&result.Str))
|
||||
// create byte slice headers
|
||||
rawh := reflect.SliceHeader{Data: rawhi.Data, Len: rawhi.Len}
|
||||
strh := reflect.SliceHeader{Data: strhi.Data, Len: strhi.Len}
|
||||
if strh.Data == 0 {
|
||||
// str is nil
|
||||
if rawh.Data == 0 {
|
||||
// raw is nil
|
||||
result.Raw = ""
|
||||
} else {
|
||||
// raw has data, safely copy the slice header to a string
|
||||
result.Raw = string(*(*[]byte)(unsafe.Pointer(&rawh)))
|
||||
}
|
||||
result.Str = ""
|
||||
} else if rawh.Data == 0 {
|
||||
// raw is nil
|
||||
result.Raw = ""
|
||||
// str has data, safely copy the slice header to a string
|
||||
result.Str = string(*(*[]byte)(unsafe.Pointer(&strh)))
|
||||
} else if strh.Data >= rawh.Data &&
|
||||
int(strh.Data)+strh.Len <= int(rawh.Data)+rawh.Len {
|
||||
// Str is a substring of Raw.
|
||||
start := int(strh.Data - rawh.Data)
|
||||
// safely copy the raw slice header
|
||||
result.Raw = string(*(*[]byte)(unsafe.Pointer(&rawh)))
|
||||
// substring the raw
|
||||
result.Str = result.Raw[start : start+strh.Len]
|
||||
} else {
|
||||
// safely copy both the raw and str slice headers to strings
|
||||
result.Raw = string(*(*[]byte)(unsafe.Pointer(&rawh)))
|
||||
result.Str = string(*(*[]byte)(unsafe.Pointer(&strh)))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// fillIndex finds the position of Raw data and assigns it to the Index field
|
||||
// of the resulting value. If the position cannot be found then Index zero is
|
||||
// used instead.
|
||||
func fillIndex(json string, c *parseContext) {
|
||||
if len(c.value.Raw) > 0 && !c.calcd {
|
||||
jhdr := *(*reflect.StringHeader)(unsafe.Pointer(&json))
|
||||
rhdr := *(*reflect.StringHeader)(unsafe.Pointer(&(c.value.Raw)))
|
||||
c.value.Index = int(rhdr.Data - jhdr.Data)
|
||||
if c.value.Index < 0 || c.value.Index >= len(json) {
|
||||
c.value.Index = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
280
gjson_test.go
280
gjson_test.go
@@ -148,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) {
|
||||
@@ -479,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" {
|
||||
@@ -970,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"`}
|
||||
@@ -1275,7 +1278,6 @@ func randomObjectOrArray(keys []string, prefix string, array bool, depth int) (s
|
||||
}
|
||||
|
||||
func randomJSON() (json string, keys []string) {
|
||||
//rand.Seed(time.Now().UnixNano())
|
||||
return randomObjectOrArray(nil, "", false, 0)
|
||||
}
|
||||
|
||||
@@ -1289,3 +1291,139 @@ func TestIssue55(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user