15 Commits

Author SHA1 Message Date
ea73b4a9b9 添加简单的test 2018-12-24 21:10:17 +08:00
98ff746add 改了gjson匹配规则. 2018-12-24 21:08:43 +08:00
ab583cb85c fix: src code error. 2018-12-24 20:03:07 +08:00
862cbc6b80 delete log rpv 2018-12-23 22:27:33 +08:00
013383b188 添加正则表达式的方法 2018-12-23 21:36:09 +08:00
fdedd9ddb2 改进正则的方法 2018-12-23 05:38:59 +08:00
8ca66dad60 改匹配模式为正则表达式 2018-12-23 05:04:37 +08:00
Josh Baker
081192fa2e Merge pull request #98 from thirstycoda/master
Added not like operator support to query
2018-10-28 08:46:04 -07:00
Josh Baker
1bd06b6ad9 Merge pull request #97 from dustinblackman/fix/getmanybytes
Fix GetManyBytes to use byte related methods
2018-10-28 08:42:31 -07:00
thirstycoda
cced0fa719 Added not like operator support to query 2018-10-28 00:20:01 +01:00
Dustin Blackman
4c7d6ff4a9 fix GetManyBytes to use byte related methods 2018-10-25 16:34:28 -04:00
tidwall
1e3f6aeaa5 Fix leftover array and map values
fixes #81
2018-08-02 08:58:17 -07:00
Josh Baker
f92dbfc6b2 Fix different reuslts on duplicate keys
fixes #79
2018-07-30 14:44:31 -07:00
Josh Baker
ba784d767a Fix string output for large integers
This fix makes calling String() on a JSON Number return the original value
as it was represented in the JSON document for signed and unsigned integers.
This ensures that very big (plus-53bit) integers are correctly returned.
Floating points maintain their previous behavior [-+]?[0-9]*\.?[0-9]*.

closes #74
2018-07-10 18:10:33 -07:00
Josh Baker
f123b34087 Add appengine support 2018-06-21 11:09:58 -07:00
5 changed files with 260 additions and 86 deletions

View File

@@ -88,13 +88,14 @@ The dot and wildcard characters can be escaped with '\\'.
```
You can also query an array for the first match by using `#[...]`, or find all matches with `#[...]#`.
Queries support the `==`, `!=`, `<`, `<=`, `>`, `>=` comparison operators and the simple pattern matching `%` operator.
Queries support the `==`, `!=`, `<`, `<=`, `>`, `>=` comparison operators and the simple pattern matching `%` (like) and `!%` (not like) operators.
```
friends.#[last=="Murphy"].first >> "Dale"
friends.#[last=="Murphy"]#.first >> ["Dale","Jane"]
friends.#[age>45]#.last >> ["Craig","Murphy"]
friends.#[first%"D*"].last >> "Murphy"
friends.#[first!%"D*"].last >> "Craig"
```
## JSON Lines

156
gjson.go
View File

@@ -6,6 +6,7 @@ import (
"encoding/json"
"errors"
"reflect"
"regexp"
"strconv"
"strings"
"sync"
@@ -13,7 +14,6 @@ import (
"time"
"unicode/utf16"
"unicode/utf8"
"unsafe"
"github.com/tidwall/match"
)
@@ -78,7 +78,20 @@ func (t Result) String() string {
case False:
return "false"
case Number:
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:
@@ -345,24 +358,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,11 +390,15 @@ func (t Result) arrayOrMap(vc byte, valueize bool) (r arrayOrMapResult) {
key = value
} else {
if valueize {
if _, ok := r.oi[key.Str]; !ok {
r.oi[key.Str] = value.Value()
}
} else {
if _, ok := r.o[key.Str]; !ok {
r.o[key.Str] = value
}
}
}
count++
} else {
if valueize {
@@ -684,6 +707,8 @@ type arrayPathResult struct {
value string
all bool
}
re *regexp.Regexp
}
func parseArrayPath(path string) (r arrayPathResult) {
@@ -719,6 +744,7 @@ func parseArrayPath(path string) (r arrayPathResult) {
path[i] == '<' ||
path[i] == '>' ||
path[i] == '%' ||
path[i] == '~' ||
path[i] == ']' {
break
}
@@ -733,7 +759,7 @@ func parseArrayPath(path string) (r arrayPathResult) {
if i < len(path) {
s = i
if path[i] == '!' {
if i < len(path)-1 && path[i+1] == '=' {
if i < len(path)-1 && (path[i+1] == '=' || path[i+1] == '%') {
i++
}
} else if path[i] == '<' || path[i] == '>' {
@@ -754,39 +780,32 @@ func parseArrayPath(path string) (r arrayPathResult) {
break
}
}
s = i
for ; i < len(path); i++ {
if path[i] == '"' {
s = i + 1
pair := path[i]
i++
s2 := i
GETREGEXPSTR:
for ; i < len(path); i++ {
if path[i] > '\\' {
if path[i] == pair {
for iend := i + 1; iend < len(path); iend++ {
if path[iend] == ' ' {
continue
}
if path[i] == '"' {
// look for an escaped slash
if path[i-1] == '\\' {
n := 0
for j := i - 2; j > s2-1; j-- {
if path[j] != '\\' {
break
}
n++
}
if n%2 == 0 {
continue
}
}
break
}
}
} else if path[i] == ']' {
if i+1 < len(path) && path[i+1] == '#' {
if path[iend] == ']' {
if iend+1 < len(path) && path[iend+1] == '#' {
r.query.all = true
}
break GETREGEXPSTR
} else {
break
}
}
}
}
if i > len(path) {
i = len(path)
}
@@ -1055,6 +1074,22 @@ func parseObject(c *parseContext, i int, path string) (int, bool) {
}
return i, false
}
func escapeRegexpString(rpv string) string {
var content []byte
lrpv := len(rpv)
for i := 0; i < lrpv; i++ {
rpvChar := rpv[i]
if rpvChar == '\\' {
content = append(content, rpv[i+1])
i++
} else {
content = append(content, rpvChar)
}
}
return string(content)
}
func queryMatches(rp *arrayPathResult, value Result) bool {
rpv := rp.query.value
if len(rpv) > 2 && rpv[0] == '"' && rpv[len(rpv)-1] == '"' {
@@ -1077,6 +1112,13 @@ func queryMatches(rp *arrayPathResult, value Result) bool {
return value.Str >= rpv
case "%":
return match.Match(value.Str, rpv)
case "!%":
return !match.Match(value.Str, rpv)
case "~":
if rp.re == nil {
rp.re = regexp.MustCompile(rpv)
}
return rp.re.MatchString(value.Str)
}
case Number:
rpvn, _ := strconv.ParseFloat(rpv, 64)
@@ -1289,7 +1331,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
@@ -1385,64 +1427,14 @@ func Get(json, path string) Result {
}
}
}
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
}
}
fillIndex(json, c)
return c.value
}
func fromBytesGet(result Result) Result {
// safely get the string headers
rawhi := *(*reflect.StringHeader)(unsafe.Pointer(&result.Raw))
strhi := *(*reflect.StringHeader)(unsafe.Pointer(&result.Str))
// create byte slice headers
rawh := reflect.SliceHeader{Data: rawhi.Data, Len: rawhi.Len}
strh := reflect.SliceHeader{Data: strhi.Data, Len: strhi.Len}
if strh.Data == 0 {
// str is nil
if rawh.Data == 0 {
// raw is nil
result.Raw = ""
} else {
// raw has data, safely copy the slice header to a string
result.Raw = string(*(*[]byte)(unsafe.Pointer(&rawh)))
}
result.Str = ""
} else if rawh.Data == 0 {
// raw is nil
result.Raw = ""
// str has data, safely copy the slice header to a string
result.Str = string(*(*[]byte)(unsafe.Pointer(&strh)))
} else if strh.Data >= rawh.Data &&
int(strh.Data)+strh.Len <= int(rawh.Data)+rawh.Len {
// Str is a substring of Raw.
start := int(strh.Data - rawh.Data)
// safely copy the raw slice header
result.Raw = string(*(*[]byte)(unsafe.Pointer(&rawh)))
// substring the raw
result.Str = result.Raw[start : start+strh.Len]
} else {
// 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
}
// 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
@@ -1654,7 +1646,11 @@ 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 {
return GetMany(string(json), path...)
res := make([]Result, len(path))
for i, path := range path {
res[i] = GetBytes(json, path)
}
return res
}
var fieldsmu sync.RWMutex

10
gjson_gae.go 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
gjson_ngae.go 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

@@ -390,6 +390,7 @@ func TestBasic1(t *testing.T) {
t.Fatalf("expected %v, got %v", 3, count)
}
}
func TestBasic2(t *testing.T) {
mtok := get(basicJSON, `loggy.programmers.#[age=101].firstName`)
if mtok.String() != "1002.3" {
@@ -399,10 +400,15 @@ func TestBasic2(t *testing.T) {
if mtok.String() != "Jason" {
t.Fatalf("expected %v, got %v", "Jason", mtok.String())
}
mtok = get(basicJSON, `loggy.programmers.#[firstName % "Bre*"].email`)
mtok = get(basicJSON, `loggy.programmers.#[firstName % "Bre.*"].email`)
if mtok.String() != "aaaa" {
t.Fatalf("expected %v, got %v", "aaaa", mtok.String())
}
mtok = get(basicJSON, `loggy.programmers.#[firstName !% "Bre.*"].email`)
if mtok.String() != "bbbb" {
t.Fatalf("expected %v, got %v", "bbbb", mtok.String())
}
mtok = get(basicJSON, `loggy.programmers.#[firstName == "Brett"].email`)
if mtok.String() != "aaaa" {
t.Fatalf("expected %v, got %v", "aaaa", mtok.String())
@@ -479,7 +485,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" {
@@ -1354,3 +1361,90 @@ null
}
}
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)
}
}
func TestRegexp(t *testing.T) {
mtok := get(`{"data": [ {"dat": "123\"", "next": [{"a": "\"32"}, {"a": "32"}]}, {"dat": "234"} ] }`, `data.#[dat ~ "3\""].next.#[a ~ @\"32@]#.a`)
if mtok.String() != `["\"32"]` {
t.Fatalf("expected %v, got %v", `"32`, mtok.String())
}
}
func TestChinese(t *testing.T) {
js := `{"data": [{"f":"\"广告"}, {"f": "广告"}]}`
mtok := get(js, `data.#[f=""广告"]#`)
if mtok.Array()[0].String() != `{"f":"\"广告"}` {
t.Error("error", mtok.Array())
}
}