Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ea73b4a9b9 | |||
| 98ff746add | |||
| ab583cb85c | |||
| 862cbc6b80 | |||
| 013383b188 | |||
| fdedd9ddb2 | |||
| 8ca66dad60 | |||
|
|
081192fa2e | ||
|
|
1bd06b6ad9 | ||
|
|
cced0fa719 | ||
|
|
4c7d6ff4a9 | ||
|
|
1e3f6aeaa5 | ||
|
|
f92dbfc6b2 | ||
|
|
ba784d767a |
@@ -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
|
||||
|
||||
107
gjson.go
107
gjson.go
@@ -6,6 +6,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -77,7 +78,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:
|
||||
@@ -344,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
|
||||
|
||||
@@ -370,9 +390,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++
|
||||
@@ -683,6 +707,8 @@ type arrayPathResult struct {
|
||||
value string
|
||||
all bool
|
||||
}
|
||||
|
||||
re *regexp.Regexp
|
||||
}
|
||||
|
||||
func parseArrayPath(path string) (r arrayPathResult) {
|
||||
@@ -718,6 +744,7 @@ func parseArrayPath(path string) (r arrayPathResult) {
|
||||
path[i] == '<' ||
|
||||
path[i] == '>' ||
|
||||
path[i] == '%' ||
|
||||
path[i] == '~' ||
|
||||
path[i] == ']' {
|
||||
break
|
||||
}
|
||||
@@ -732,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] == '>' {
|
||||
@@ -753,39 +780,32 @@ func parseArrayPath(path string) (r arrayPathResult) {
|
||||
break
|
||||
}
|
||||
}
|
||||
s = i
|
||||
|
||||
s = i + 1
|
||||
pair := path[i]
|
||||
i++
|
||||
GETREGEXPSTR:
|
||||
for ; i < len(path); i++ {
|
||||
if path[i] == '"' {
|
||||
i++
|
||||
s2 := i
|
||||
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
|
||||
}
|
||||
|
||||
if path[iend] == ']' {
|
||||
if iend+1 < len(path) && path[iend+1] == '#' {
|
||||
r.query.all = true
|
||||
}
|
||||
break GETREGEXPSTR
|
||||
} else {
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
} else if path[i] == ']' {
|
||||
if i+1 < len(path) && path[i+1] == '#' {
|
||||
r.query.all = true
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if i > len(path) {
|
||||
i = len(path)
|
||||
}
|
||||
@@ -1054,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] == '"' {
|
||||
@@ -1076,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)
|
||||
@@ -1288,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
|
||||
@@ -1603,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
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user