Removed Multi. Added Parse and result.Get funcs

The Multi field was too bulky. fixes #4
Added a Parse(json) function that will do a simple parse of json.
Added a result.Get(path) function that returns a child result.
Added Bool(), Int(), and Float() to result type. fixes #5
This commit is contained in:
Josh Baker 2016-08-22 06:05:51 -07:00
parent 44a8706c89
commit cd422a3e10
3 changed files with 428 additions and 61 deletions

View File

@ -56,7 +56,7 @@ To access an array value use the index as the key.
To get the number of elements in an array or to access a child path, 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 '\'.
``` ```json
{ {
"name": {"first": "Tom", "last": "Anderson"}, "name": {"first": "Tom", "last": "Anderson"},
"age":37, "age":37,
@ -93,16 +93,6 @@ string, for JSON string literals
nil, for JSON null nil, for JSON null
``` ```
To get the Go value call the `Value()` method:
```go
result.Value() // interface{} which may be nil, string, float64, or bool
// Or just get the value in one step.
gjson.Get(json, "name.last").Value()
```
To directly access the value: To directly access the value:
```go ```go
@ -113,6 +103,30 @@ result.Raw // holds the raw json
result.Multi // holds nested array values result.Multi // holds nested array values
``` ```
There are a variety of handy functions that work on a result:
```go
result.Value() interface{}
result.Int() int64
result.Float() float64
result.String() string
result.Bool() bool
result.Array() []gjson.Result
result.Map() map[string]gjson.Result
result.Get(path string) Result
```
The `result.Value()` function returns an `interface{}` which requires type assertion and is one of the following Go types:
```go
boolean >> bool
number >> float64
string >> string
null >> nil
array >> []interface{}
object >> map[string]interface{}
```
## Get nested array values ## Get nested array values
Suppose you want all the last names from the following json: Suppose you want all the last names from the following json:
@ -138,11 +152,23 @@ You would use the path "programmers.#.lastName" like such:
```go ```go
result := gjson.Get(json, "programmers.#.lastName") result := gjson.Get(json, "programmers.#.lastName")
for _,name := range result.Multi { for _,name := range result.Array() {
println(name.String()) println(name.String())
} }
``` ```
## Simple Parse and Get
There's a `Parse(json)` function that will do a simple parse, and `result.Get(path)` that will search a result.
For example, all of these will return the same result:
```go
Parse(json).Get("name").Get("last")
Get("name").Get("last")
Get("name.last")
```
## Check for the existence of a value ## Check for the existence of a value
Sometimes you may want to see if the value actually existed in the json document. Sometimes you may want to see if the value actually existed in the json document.

373
gjson.go
View File

@ -19,8 +19,6 @@ 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().
@ -33,8 +31,6 @@ 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.
@ -48,15 +44,6 @@ 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:
@ -64,6 +51,316 @@ func (t Result) String() string {
} }
} }
// Bool returns an boolean representation.
func (t Result) Bool() bool {
switch t.Type {
default:
return false
case True:
return true
case String:
return t.Str != "" && t.Str != "0"
case Number:
return t.Num != 0
}
}
// Int returns an integer representation.
func (t Result) Int() int64 {
switch t.Type {
default:
return 0
case True:
return 1
case String:
n, _ := strconv.ParseInt(t.Str, 10, 64)
return n
case Number:
return int64(t.Num)
}
}
// Float returns an float64 representation.
func (t Result) Float() float64 {
switch t.Type {
default:
return 0
case True:
return 1
case String:
n, _ := strconv.ParseFloat(t.Str, 64)
return n
case Number:
return t.Num
}
}
// Array returns back an array of children. The result must be a JSON array.
func (t Result) Array() []Result {
if t.Type != JSON {
return nil
}
a, _, _ := t.arrayOrMap('[')
return a
}
// Map returns back an map of children. The result should be a JSON array.
func (t Result) Map() map[string]Result {
if t.Type != JSON {
return map[string]Result{}
}
_, o, _ := t.arrayOrMap('{')
return o
}
// Get searches result for the specified path.
// The result should be a JSON array or object.
func (t Result) Get(path string) Result {
return Get(t.Raw, path)
}
func (t Result) arrayOrMap(vc byte) ([]Result, map[string]Result, byte) {
var a = []Result{}
var o = map[string]Result{}
var json = t.Raw
var i int
var value Result
var count int
var key Result
if vc == 0 {
for ; i < len(json); i++ {
if json[i] == '{' || json[i] == '[' {
vc = json[i]
i++
break
}
if json[i] > ' ' {
goto end
}
}
} else {
for ; i < len(json); i++ {
if json[i] == vc {
i++
break
}
if json[i] > ' ' {
goto end
}
}
}
for ; i < len(json); i++ {
if json[i] <= ' ' {
continue
}
// get next value
if json[i] == ']' || json[i] == '}' {
break
}
switch json[i] {
default:
if (json[i] >= '0' && json[i] <= '9') || json[i] == '-' {
value.Type = Number
value.Raw, value.Num = tonum(json[i:])
} else {
continue
}
case '{', '[':
value.Type = JSON
value.Raw = squash(json[i:])
case 'n':
value.Type = Null
value.Raw = tolit(json[i:])
case 't':
value.Type = True
value.Raw = tolit(json[i:])
case 'f':
value.Type = False
value.Raw = tolit(json[i:])
case '"':
value.Type = String
value.Raw, value.Str = tostr(json[i:])
}
i += len(value.Raw) - 1
if vc == '{' {
if count%2 == 0 {
key = value
} else {
o[key.Str] = value
}
count++
} else {
a = append(a, value)
}
}
end:
return a, o, vc
}
// Parse parses the json and returns a result
func Parse(json string) Result {
var value Result
for i := 0; i < len(json); i++ {
if json[i] <= ' ' {
continue
}
switch json[i] {
default:
if (json[i] >= '0' && json[i] <= '9') || json[i] == '-' {
value.Type = Number
value.Raw, value.Num = tonum(json[i:])
} else {
return Result{}
}
case '{', '[':
value.Type = JSON
value.Raw = json[i:]
// we just trim the tail end
for value.Raw[len(value.Raw)-1] <= ' ' {
value.Raw = value.Raw[:len(value.Raw)-1]
}
case 'n':
value.Type = Null
value.Raw = tolit(json[i:])
case 't':
value.Type = True
value.Raw = tolit(json[i:])
case 'f':
value.Type = False
value.Raw = tolit(json[i:])
case '"':
value.Type = String
value.Raw, value.Str = tostr(json[i:])
}
break
}
return value
}
func squash(json string) string {
// expects that the lead character is a '[' or '{'
// squash the value, ignoring all nested arrays and objects.
// the first '[' or '{' has already been read
depth := 1
for i := 1; i < len(json); i++ {
if json[i] >= '"' && json[i] <= '}' {
switch json[i] {
case '"':
i++
s2 := i
for ; i < len(json); i++ {
if json[i] > '\\' {
continue
}
if json[i] == '"' {
// look for an escaped slash
if json[i-1] == '\\' {
n := 0
for j := i - 2; j > s2-1; j-- {
if json[j] != '\\' {
break
}
n++
}
if n%2 == 0 {
continue
}
}
break
}
}
case '{', '[':
depth++
case '}', ']':
depth--
if depth == 0 {
return json[:i+1]
}
}
}
}
return json
}
func tonum(json string) (raw string, num float64) {
for i := 1; i < len(json); i++ {
// less than dash might have valid characters
if json[i] <= '-' {
if json[i] <= ' ' || json[i] == ',' {
// break on whitespace and comma
raw = json[:i]
num, _ = strconv.ParseFloat(raw, 64)
return
}
// could be a '+' or '-'. let's assume so.
continue
}
if json[i] < ']' {
// probably a valid number
continue
}
if json[i] == 'e' || json[i] == 'E' {
// allow for exponential numbers
continue
}
// likely a ']' or '}'
raw = json[:i]
num, _ = strconv.ParseFloat(raw, 64)
return
}
raw = json
num, _ = strconv.ParseFloat(raw, 64)
return
}
func tolit(json string) (raw string) {
for i := 1; i < len(json); i++ {
if json[i] <= 'a' || json[i] >= 'z' {
return json[:i]
}
}
return json
}
func tostr(json string) (raw string, str string) {
// expects that the lead character is a '"'
for i := 1; i < len(json); i++ {
if json[i] > '\\' {
continue
}
if json[i] == '"' {
return json[:i+1], json[1:i]
}
if json[i] == '\\' {
i++
for ; i < len(json); i++ {
if json[i] > '\\' {
continue
}
if json[i] == '"' {
// look for an escaped slash
if json[i-1] == '\\' {
n := 0
for j := i - 2; j > 0; j-- {
if json[j] != '\\' {
break
}
n++
}
if n%2 == 0 {
continue
}
}
break
}
}
return json[:i+1], unescape(json[1:i])
}
}
return json, json[1:]
}
// Exists returns true if value exists. // Exists returns true if value exists.
// //
// if gjson.Get(json, "name.last").Exists(){ // if gjson.Get(json, "name.last").Exists(){
@ -92,17 +389,26 @@ func (t Result) Value() interface{} {
return false return false
case Number: case Number:
return t.Num return t.Num
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 a, o, vc := t.arrayOrMap(0)
if vc == '{' {
var m = map[string]interface{}{}
for k, v := range o {
m[k] = v.Value()
}
return m
} else if vc == '[' {
var m = make([]interface{}, 0, len(a))
for _, v := range a {
m = append(m, v.Value())
}
return m
}
return nil
case True: case True:
return true return true
} }
} }
type part struct { type part struct {
@ -518,6 +824,9 @@ proc_val:
// the first double-quote has already been read // the first double-quote has already been read
s = i s = i
for ; i < len(json); i++ { for ; i < len(json); i++ {
if json[i] > '\\' {
continue
}
if json[i] == '"' { if json[i] == '"' {
value.Raw = json[s-1 : i+1] value.Raw = json[s-1 : i+1]
value.Str = json[s:i] value.Str = json[s:i]
@ -598,14 +907,19 @@ proc_val:
case '}', ']': case '}', ']':
if arrch && parts[depth-1].key == "#" { if arrch && parts[depth-1].key == "#" {
if alogok { if alogok {
result := Result{Type: Multi} var jsons = make([]byte, 0, 64)
jsons = append(jsons, '[')
for j := 0; j < len(alog); j++ { for j := 0; j < len(alog); j++ {
res := Get(json[alog[j]:], alogkey) res := Get(json[alog[j]:], alogkey)
if res.Exists() { if res.Exists() {
result.Multi = append(result.Multi, res) if j > 0 {
jsons = append(jsons, ',')
}
jsons = append(jsons, []byte(res.Raw)...)
} }
} }
return result jsons = append(jsons, ']')
return Result{Type: JSON, Raw: string(jsons)}
} else { } else {
return Result{Type: Number, Num: float64(f.count)} return Result{Type: Number, Num: float64(f.count)}
} }
@ -689,7 +1003,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 < Multi // Null < False < Number < String < True < JSON
// //
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 {
@ -707,17 +1021,6 @@ func (t Result) Less(token Result, caseSensitive bool) bool {
if t.Type == Number { if t.Type == Number {
return t.Num < token.Num return t.Num < token.Num
} }
if t.Type == 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)
}
return t.Raw < token.Raw return t.Raw < token.Raw
} }

View File

@ -116,32 +116,64 @@ var basicJSON = `{"age":100, "name":{"here":"B\\\"R"},
"firstName": "Elliotte", "firstName": "Elliotte",
"lastName": "Harold", "lastName": "Harold",
"email": "cccc" "email": "cccc"
} },
{
"firstName": 1002.3
}
] ]
} }
}` }`
func TestBasic(t *testing.T) { func TestBasic(t *testing.T) {
var mtok Result
mtok = Get(basicJSON, "loggy")
if mtok.Type != JSON {
t.Fatalf("expected %v, got %v", JSON, mtok.Type)
}
if len(mtok.Map()) != 1 {
t.Fatalf("expected %v, got %v", 1, len(mtok.Map()))
}
programmers := mtok.Map()["programmers"]
if programmers.Array()[1].Map()["firstName"].Str != "Jason" {
t.Fatalf("expected %v, got %v", "Jason", mtok.Map()["programmers"].Array()[1].Map()["firstName"].Str)
}
if Parse(basicJSON).Get("loggy.programmers").Get("1").Get("firstName").Str != "Jason" {
t.Fatalf("expected %v, got %v", "Jason", Parse(basicJSON).Get("loggy.programmers").Get("1").Get("firstName").Str)
}
var token Result var token Result
mtok := Get(basicJSON, "loggy.programmers.#.firstName") if token = Parse("-102"); token.Num != -102 {
if mtok.Type != Multi { t.Fatal("expected %v, got %v", -102, token.Num)
t.Fatal("expected %v, got %v", Multi, mtok.Type)
} }
if len(mtok.Multi) != 3 { if token = Parse("102"); token.Num != 102 {
t.Fatalf("expected 3, got %v", len(mtok.Multi)) t.Fatal("expected %v, got %v", 102, token.Num)
} }
for i, ex := range []string{"Brett", "Jason", "Elliotte"} { if token = Parse("102.2"); token.Num != 102.2 {
if mtok.Multi[i].String() != ex { t.Fatal("expected %v, got %v", 102.2, token.Num)
t.Fatalf("expected '%v', got '%v'", ex, mtok.Multi[i].String()) }
if token = Parse(`"hello"`); token.Str != "hello" {
t.Fatal("expected %v, got %v", "hello", token.Str)
}
if token = Parse(`"\"he\nllo\""`); token.Str != "\"he\nllo\"" {
t.Fatal("expected %v, got %v", "\"he\nllo\"", token.Str)
}
mtok = Get(basicJSON, "loggy.programmers.#.firstName")
if len(mtok.Array()) != 4 {
t.Fatalf("expected 4, got %v", len(mtok.Array()))
}
for i, ex := range []string{"Brett", "Jason", "Elliotte", "1002.3"} {
if mtok.Array()[i].String() != ex {
t.Fatalf("expected '%v', got '%v'", ex, mtok.Array()[i].String())
} }
} }
mtok = Get(basicJSON, "loggy.programmers.#.asd") mtok = Get(basicJSON, "loggy.programmers.#.asd")
if mtok.Type != Multi { if mtok.Type != JSON {
t.Fatal("expected %v, got %v", Multi, mtok.Type) t.Fatal("expected %v, got %v", JSON, mtok.Type)
} }
if len(mtok.Multi) != 0 { if len(mtok.Array()) != 0 {
t.Fatalf("expected 0, got %v", len(mtok.Multi)) t.Fatalf("expected 0, got %v", len(mtok.Array()))
} }
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)
} }
@ -201,13 +233,19 @@ func TestBasic(t *testing.T) {
if token.String() != `{"what is a wren?":"a bird"}` { if token.String() != `{"what is a wren?":"a bird"}` {
t.Fatal("expecting '"+`{"what is a wren?":"a bird"}`+"'", "got", token.String()) t.Fatal("expecting '"+`{"what is a wren?":"a bird"}`+"'", "got", token.String())
} }
_ = token.Value().(string) _ = token.Value().(map[string]interface{})
if Get(basicJSON, "").Value() != nil { if Get(basicJSON, "").Value() != nil {
t.Fatal("should be nil") t.Fatal("should be nil")
} }
Get(basicJSON, "vals.hello") Get(basicJSON, "vals.hello")
mm := Parse(basicJSON).Value().(map[string]interface{})
fn := mm["loggy"].(map[string]interface{})["programmers"].([]interface{})[1].(map[string]interface{})["firstName"].(string)
if fn != "Jason" {
t.Fatalf("expecting %v, got %v", "Jason", fn)
}
} }
func TestUnescape(t *testing.T) { func TestUnescape(t *testing.T) {