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 key may contain special wildcard characters '\*' and '?'.
|
||||
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 '\'.
|
||||
```
|
||||
{
|
||||
"name": {"first": "Tom", "last": "Anderson"},
|
||||
"age":37,
|
||||
"children": ["Sara","Alex","Jack"],
|
||||
"fav.movie": "Deer Hunter"
|
||||
"fav.movie": "Deer Hunter",
|
||||
"friends": [
|
||||
{"first": "James", "last": "Murphy"},
|
||||
{"first": "Roger", "last": "Craig"}
|
||||
]
|
||||
}
|
||||
"name.last" >> "Anderson"
|
||||
"age" >> 37
|
||||
|
@ -67,8 +71,12 @@ The dot and wildcard characters can be escaped with '\'.
|
|||
"child*.2" >> "Jack"
|
||||
"c?ildren.0" >> "Sara"
|
||||
"fav\.movie" >> "Deer Hunter"
|
||||
"friends.#.first" >> [ "James", "Roger" ]
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
## Result Type
|
||||
|
||||
GJSON supports the json types `string`, `number`, `bool`, and `null`.
|
||||
|
|
73
gjson.go
73
gjson.go
|
@ -19,6 +19,8 @@ const (
|
|||
True
|
||||
// JSON is a raw block of JSON
|
||||
JSON
|
||||
// Multi is a subset of results
|
||||
Multi
|
||||
)
|
||||
|
||||
// Result represents a json value that is returned from Get().
|
||||
|
@ -31,6 +33,8 @@ type Result struct {
|
|||
Str string
|
||||
// Num is the json number
|
||||
Num float64
|
||||
// Multi is the subset of results
|
||||
Multi []Result
|
||||
}
|
||||
|
||||
// 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)
|
||||
case String:
|
||||
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:
|
||||
return t.Raw
|
||||
case True:
|
||||
|
@ -78,6 +91,12 @@ func (t Result) Value() interface{} {
|
|||
return t.Num
|
||||
case String:
|
||||
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:
|
||||
return t.Raw
|
||||
case True:
|
||||
|
@ -105,13 +124,17 @@ type frame struct {
|
|||
// A path is a series of keys seperated by a dot.
|
||||
// A key may contain special wildcard characters '*' and '?'.
|
||||
// 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 '\'.
|
||||
//
|
||||
// {
|
||||
// "name": {"first": "Tom", "last": "Anderson"},
|
||||
// "age":37,
|
||||
// "children": ["Sara","Alex","Jack"]
|
||||
// "children": ["Sara","Alex","Jack"],
|
||||
// "friends": [
|
||||
// {"first": "James", "last": "Murphy"},
|
||||
// {"first": "Roger", "last": "Craig"}
|
||||
// ]
|
||||
// }
|
||||
// "name.last" >> "Anderson"
|
||||
// "age" >> 37
|
||||
|
@ -119,11 +142,16 @@ type frame struct {
|
|||
// "children.1" >> "Alex"
|
||||
// "child*.2" >> "Jack"
|
||||
// "c?ildren.0" >> "Sara"
|
||||
// "friends.#.first" >> [ "James", "Roger" ]
|
||||
//
|
||||
func Get(json string, path string) Result {
|
||||
var s int // starting index variable
|
||||
var wild bool // wildcard indicator
|
||||
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 {
|
||||
// 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] == '?' {
|
||||
// set the wild flag to indicate that the part is a wildcard.
|
||||
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] == '\\' {
|
||||
// go into escape mode. this is a slower path that
|
||||
// strips off the escape character from the part.
|
||||
|
@ -224,6 +259,10 @@ read_key:
|
|||
// key for the 10th value.
|
||||
f.key = strconv.FormatInt(int64(f.count), 10)
|
||||
f.count++
|
||||
if alogok && depth == len(parts) {
|
||||
alog = append(alog, i)
|
||||
}
|
||||
//f.alog = append(f.alog, i)
|
||||
} else {
|
||||
// for objects we must parse the next string. this string will
|
||||
// become the key that is compared against the path parts.
|
||||
|
@ -501,8 +540,7 @@ proc_val:
|
|||
}
|
||||
return value
|
||||
} else {
|
||||
f.stype = vc
|
||||
f.count = 0
|
||||
f = frame{stype: vc}
|
||||
stack = append(stack, f)
|
||||
depth++
|
||||
goto read_key
|
||||
|
@ -539,8 +577,19 @@ proc_val:
|
|||
for ; i < len(json); i++ {
|
||||
switch json[i] {
|
||||
case '}', ']':
|
||||
if parts[depth-1].key == "#" {
|
||||
return Result{Type: Number, Num: float64(f.count)}
|
||||
if arrch && parts[depth-1].key == "#" {
|
||||
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
|
||||
depth--
|
||||
|
@ -621,7 +670,7 @@ func unescape(json string) string { //, error) {
|
|||
// The caseSensitive paramater is used when the tokens are Strings.
|
||||
// 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 {
|
||||
if t.Type < token.Type {
|
||||
|
@ -638,6 +687,16 @@ func (t Result) Less(token Result, caseSensitive bool) bool {
|
|||
return 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:
|
||||
return t.Num < token.Num
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ import (
|
|||
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.
|
||||
func TestRandomData(t *testing.T) {
|
||||
var lstr string
|
||||
|
@ -99,10 +99,49 @@ var basicJSON = `{"age":100, "name":{"here":"B\\\"R"},
|
|||
"happy":true,"immortal":false,
|
||||
"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],
|
||||
"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) {
|
||||
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 {
|
||||
t.Fatalf("expected 3, got %v", Get(basicJSON, "items.3.tags.#").Num)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue