mirror of https://github.com/tidwall/gjson.git
get nested array values, fixes #4
To get all the friends last names use: "friends.#.last" { "friends": [ {"first": "James", "last": "Murphy"}, {"first": "Roger", "last": "Craig"} ] }
This commit is contained in:
parent
260ef19a2e
commit
0757a4d1e7
12
README.md
12
README.md
|
@ -51,14 +51,18 @@ Prichard
|
||||||
A path is a series of keys separated by a dot.
|
A path is a series of keys separated by a dot.
|
||||||
A key may contain special wildcard characters '\*' and '?'.
|
A key may contain special wildcard characters '\*' and '?'.
|
||||||
To access an array value use the index as the key.
|
To access an array value use the index as the key.
|
||||||
To get the number of elements in an array use the '#' character.
|
To get the number of elements in an array or to access a child path, use the '#' character.
|
||||||
The dot and wildcard characters can be escaped with '\'.
|
The dot and wildcard characters can be escaped with '\'.
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"name": {"first": "Tom", "last": "Anderson"},
|
"name": {"first": "Tom", "last": "Anderson"},
|
||||||
"age":37,
|
"age":37,
|
||||||
"children": ["Sara","Alex","Jack"],
|
"children": ["Sara","Alex","Jack"],
|
||||||
"fav.movie": "Deer Hunter"
|
"fav.movie": "Deer Hunter",
|
||||||
|
"friends": [
|
||||||
|
{"first": "James", "last": "Murphy"},
|
||||||
|
{"first": "Roger", "last": "Craig"}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
"name.last" >> "Anderson"
|
"name.last" >> "Anderson"
|
||||||
"age" >> 37
|
"age" >> 37
|
||||||
|
@ -67,8 +71,12 @@ The dot and wildcard characters can be escaped with '\'.
|
||||||
"child*.2" >> "Jack"
|
"child*.2" >> "Jack"
|
||||||
"c?ildren.0" >> "Sara"
|
"c?ildren.0" >> "Sara"
|
||||||
"fav\.movie" >> "Deer Hunter"
|
"fav\.movie" >> "Deer Hunter"
|
||||||
|
"friends.#.first" >> [ "James", "Roger" ]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Result Type
|
## Result Type
|
||||||
|
|
||||||
GJSON supports the json types `string`, `number`, `bool`, and `null`.
|
GJSON supports the json types `string`, `number`, `bool`, and `null`.
|
||||||
|
|
73
gjson.go
73
gjson.go
|
@ -19,6 +19,8 @@ const (
|
||||||
True
|
True
|
||||||
// JSON is a raw block of JSON
|
// JSON is a raw block of JSON
|
||||||
JSON
|
JSON
|
||||||
|
// Multi is a subset of results
|
||||||
|
Multi
|
||||||
)
|
)
|
||||||
|
|
||||||
// Result represents a json value that is returned from Get().
|
// Result represents a json value that is returned from Get().
|
||||||
|
@ -31,6 +33,8 @@ type Result struct {
|
||||||
Str string
|
Str string
|
||||||
// Num is the json number
|
// Num is the json number
|
||||||
Num float64
|
Num float64
|
||||||
|
// Multi is the subset of results
|
||||||
|
Multi []Result
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns a string representation of the value.
|
// String returns a string representation of the value.
|
||||||
|
@ -44,6 +48,15 @@ func (t Result) String() string {
|
||||||
return strconv.FormatFloat(t.Num, 'f', -1, 64)
|
return strconv.FormatFloat(t.Num, 'f', -1, 64)
|
||||||
case String:
|
case String:
|
||||||
return t.Str
|
return t.Str
|
||||||
|
case Multi:
|
||||||
|
var str string
|
||||||
|
for i, res := range t.Multi {
|
||||||
|
if i > 0 {
|
||||||
|
str += ","
|
||||||
|
}
|
||||||
|
str += res.String()
|
||||||
|
}
|
||||||
|
return str
|
||||||
case JSON:
|
case JSON:
|
||||||
return t.Raw
|
return t.Raw
|
||||||
case True:
|
case True:
|
||||||
|
@ -78,6 +91,12 @@ func (t Result) Value() interface{} {
|
||||||
return t.Num
|
return t.Num
|
||||||
case String:
|
case String:
|
||||||
return t.Str
|
return t.Str
|
||||||
|
case Multi:
|
||||||
|
var res = make([]interface{}, len(t.Multi))
|
||||||
|
for i, v := range t.Multi {
|
||||||
|
res[i] = v.Value()
|
||||||
|
}
|
||||||
|
return res
|
||||||
case JSON:
|
case JSON:
|
||||||
return t.Raw
|
return t.Raw
|
||||||
case True:
|
case True:
|
||||||
|
@ -105,13 +124,17 @@ type frame struct {
|
||||||
// A path is a series of keys seperated by a dot.
|
// A path is a series of keys seperated by a dot.
|
||||||
// A key may contain special wildcard characters '*' and '?'.
|
// A key may contain special wildcard characters '*' and '?'.
|
||||||
// To access an array value use the index as the key.
|
// To access an array value use the index as the key.
|
||||||
// To get the number of elements in an array use the '#' character.
|
// To get the number of elements in an array or to access a child path, use the '#' character.
|
||||||
// The dot and wildcard character can be escaped with '\'.
|
// The dot and wildcard character can be escaped with '\'.
|
||||||
//
|
//
|
||||||
// {
|
// {
|
||||||
// "name": {"first": "Tom", "last": "Anderson"},
|
// "name": {"first": "Tom", "last": "Anderson"},
|
||||||
// "age":37,
|
// "age":37,
|
||||||
// "children": ["Sara","Alex","Jack"]
|
// "children": ["Sara","Alex","Jack"],
|
||||||
|
// "friends": [
|
||||||
|
// {"first": "James", "last": "Murphy"},
|
||||||
|
// {"first": "Roger", "last": "Craig"}
|
||||||
|
// ]
|
||||||
// }
|
// }
|
||||||
// "name.last" >> "Anderson"
|
// "name.last" >> "Anderson"
|
||||||
// "age" >> 37
|
// "age" >> 37
|
||||||
|
@ -119,11 +142,16 @@ type frame struct {
|
||||||
// "children.1" >> "Alex"
|
// "children.1" >> "Alex"
|
||||||
// "child*.2" >> "Jack"
|
// "child*.2" >> "Jack"
|
||||||
// "c?ildren.0" >> "Sara"
|
// "c?ildren.0" >> "Sara"
|
||||||
|
// "friends.#.first" >> [ "James", "Roger" ]
|
||||||
//
|
//
|
||||||
func Get(json string, path string) Result {
|
func Get(json string, path string) Result {
|
||||||
var s int // starting index variable
|
var s int // starting index variable
|
||||||
var wild bool // wildcard indicator
|
var wild bool // wildcard indicator
|
||||||
var parts = make([]part, 0, 4) // parsed path parts
|
var parts = make([]part, 0, 4) // parsed path parts
|
||||||
|
var arrch bool
|
||||||
|
var alogok bool
|
||||||
|
var alogkey string
|
||||||
|
var alog []int
|
||||||
|
|
||||||
if len(path) == 0 {
|
if len(path) == 0 {
|
||||||
// do nothing when no path specified and return an empty result.
|
// do nothing when no path specified and return an empty result.
|
||||||
|
@ -148,6 +176,13 @@ func Get(json string, path string) Result {
|
||||||
} else if path[i] == '*' || path[i] == '?' {
|
} else if path[i] == '*' || path[i] == '?' {
|
||||||
// set the wild flag to indicate that the part is a wildcard.
|
// set the wild flag to indicate that the part is a wildcard.
|
||||||
wild = true
|
wild = true
|
||||||
|
} else if path[i] == '#' {
|
||||||
|
arrch = true
|
||||||
|
if s == i && i+1 < len(path) && path[i+1] == '.' {
|
||||||
|
alogok = true
|
||||||
|
alogkey = path[i+2:]
|
||||||
|
path = path[:i+1]
|
||||||
|
}
|
||||||
} else if path[i] == '\\' {
|
} else if path[i] == '\\' {
|
||||||
// go into escape mode. this is a slower path that
|
// go into escape mode. this is a slower path that
|
||||||
// strips off the escape character from the part.
|
// strips off the escape character from the part.
|
||||||
|
@ -224,6 +259,10 @@ read_key:
|
||||||
// key for the 10th value.
|
// key for the 10th value.
|
||||||
f.key = strconv.FormatInt(int64(f.count), 10)
|
f.key = strconv.FormatInt(int64(f.count), 10)
|
||||||
f.count++
|
f.count++
|
||||||
|
if alogok && depth == len(parts) {
|
||||||
|
alog = append(alog, i)
|
||||||
|
}
|
||||||
|
//f.alog = append(f.alog, i)
|
||||||
} else {
|
} else {
|
||||||
// for objects we must parse the next string. this string will
|
// for objects we must parse the next string. this string will
|
||||||
// become the key that is compared against the path parts.
|
// become the key that is compared against the path parts.
|
||||||
|
@ -501,8 +540,7 @@ proc_val:
|
||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
} else {
|
} else {
|
||||||
f.stype = vc
|
f = frame{stype: vc}
|
||||||
f.count = 0
|
|
||||||
stack = append(stack, f)
|
stack = append(stack, f)
|
||||||
depth++
|
depth++
|
||||||
goto read_key
|
goto read_key
|
||||||
|
@ -539,8 +577,19 @@ proc_val:
|
||||||
for ; i < len(json); i++ {
|
for ; i < len(json); i++ {
|
||||||
switch json[i] {
|
switch json[i] {
|
||||||
case '}', ']':
|
case '}', ']':
|
||||||
if parts[depth-1].key == "#" {
|
if arrch && parts[depth-1].key == "#" {
|
||||||
return Result{Type: Number, Num: float64(f.count)}
|
if alogok {
|
||||||
|
result := Result{Type: Multi}
|
||||||
|
for j := 0; j < len(alog); j++ {
|
||||||
|
res := Get(json[alog[j]:], alogkey)
|
||||||
|
if res.Exists() {
|
||||||
|
result.Multi = append(result.Multi, res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
} else {
|
||||||
|
return Result{Type: Number, Num: float64(f.count)}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// step the stack back
|
// step the stack back
|
||||||
depth--
|
depth--
|
||||||
|
@ -621,7 +670,7 @@ func unescape(json string) string { //, error) {
|
||||||
// The caseSensitive paramater is used when the tokens are Strings.
|
// The caseSensitive paramater is used when the tokens are Strings.
|
||||||
// The order when comparing two different type is:
|
// The order when comparing two different type is:
|
||||||
//
|
//
|
||||||
// Null < False < Number < String < True < JSON
|
// Null < False < Number < String < True < JSON < Multi
|
||||||
//
|
//
|
||||||
func (t Result) Less(token Result, caseSensitive bool) bool {
|
func (t Result) Less(token Result, caseSensitive bool) bool {
|
||||||
if t.Type < token.Type {
|
if t.Type < token.Type {
|
||||||
|
@ -638,6 +687,16 @@ func (t Result) Less(token Result, caseSensitive bool) bool {
|
||||||
return t.Str < token.Str
|
return t.Str < token.Str
|
||||||
}
|
}
|
||||||
return stringLessInsensitive(t.Str, token.Str)
|
return stringLessInsensitive(t.Str, token.Str)
|
||||||
|
case Multi:
|
||||||
|
for i := 0; i < len(t.Multi) && i < len(token.Multi); i++ {
|
||||||
|
if t.Multi[i].Less(token.Multi[i], caseSensitive) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if token.Multi[i].Less(t.Multi[i], caseSensitive) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len(t.Multi) < len(token.Multi)
|
||||||
case Number:
|
case Number:
|
||||||
return t.Num < token.Num
|
return t.Num < token.Num
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ import (
|
||||||
fflib "github.com/pquerna/ffjson/fflib/v1"
|
fflib "github.com/pquerna/ffjson/fflib/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestRandomData is a fuzzing test that throughs random data at the Parse
|
// TestRandomData is a fuzzing test that throws random data at the Parse
|
||||||
// function looking for panics.
|
// function looking for panics.
|
||||||
func TestRandomData(t *testing.T) {
|
func TestRandomData(t *testing.T) {
|
||||||
var lstr string
|
var lstr string
|
||||||
|
@ -99,10 +99,49 @@ var basicJSON = `{"age":100, "name":{"here":"B\\\"R"},
|
||||||
"happy":true,"immortal":false,
|
"happy":true,"immortal":false,
|
||||||
"items":[1,2,3,{"tags":[1,2,3],"points":[[1,2],[3,4]]},4,5,6,7],
|
"items":[1,2,3,{"tags":[1,2,3],"points":[[1,2],[3,4]]},4,5,6,7],
|
||||||
"arr":["1",2,"3",{"hello":"world"},"4",5],
|
"arr":["1",2,"3",{"hello":"world"},"4",5],
|
||||||
"vals":[1,2,3,{"sadf":sdf"asdf"}],"name":{"first":"tom","last":null}}`
|
"vals":[1,2,3,{"sadf":sdf"asdf"}],"name":{"first":"tom","last":null},
|
||||||
|
"loggy":{
|
||||||
|
"programmers": [
|
||||||
|
{
|
||||||
|
"firstName": "Brett",
|
||||||
|
"lastName": "McLaughlin",
|
||||||
|
"email": "aaaa"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"firstName": "Jason",
|
||||||
|
"lastName": "Hunter",
|
||||||
|
"email": "bbbb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"firstName": "Elliotte",
|
||||||
|
"lastName": "Harold",
|
||||||
|
"email": "cccc"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
func TestBasic(t *testing.T) {
|
func TestBasic(t *testing.T) {
|
||||||
var token Result
|
var token Result
|
||||||
|
mtok := Get(basicJSON, "loggy.programmers.#.firstName")
|
||||||
|
if mtok.Type != Multi {
|
||||||
|
t.Fatal("expected %v, got %v", Multi, mtok.Type)
|
||||||
|
}
|
||||||
|
if len(mtok.Multi) != 3 {
|
||||||
|
t.Fatalf("expected 3, got %v", len(mtok.Multi))
|
||||||
|
}
|
||||||
|
for i, ex := range []string{"Brett", "Jason", "Elliotte"} {
|
||||||
|
if mtok.Multi[i].String() != ex {
|
||||||
|
t.Fatalf("expected '%v', got '%v'", ex, mtok.Multi[i].String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mtok = Get(basicJSON, "loggy.programmers.#.asd")
|
||||||
|
if mtok.Type != Multi {
|
||||||
|
t.Fatal("expected %v, got %v", Multi, mtok.Type)
|
||||||
|
}
|
||||||
|
if len(mtok.Multi) != 0 {
|
||||||
|
t.Fatalf("expected 0, got %v", len(mtok.Multi))
|
||||||
|
}
|
||||||
if Get(basicJSON, "items.3.tags.#").Num != 3 {
|
if Get(basicJSON, "items.3.tags.#").Num != 3 {
|
||||||
t.Fatalf("expected 3, got %v", Get(basicJSON, "items.3.tags.#").Num)
|
t.Fatalf("expected 3, got %v", Get(basicJSON, "items.3.tags.#").Num)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue