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.
The dot and wildcard characters can be escaped with '\'.
```
```json
{
"name": {"first": "Tom", "last": "Anderson"},
"age":37,
@ -93,16 +93,6 @@ string, for JSON string literals
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:
```go
@ -113,6 +103,30 @@ result.Raw // holds the raw json
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
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
result := gjson.Get(json, "programmers.#.lastName")
for _,name := range result.Multi {
for _,name := range result.Array() {
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
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
// 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().
@ -33,8 +31,6 @@ 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.
@ -48,15 +44,6 @@ 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:
@ -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.
//
// if gjson.Get(json, "name.last").Exists(){
@ -92,17 +389,26 @@ func (t Result) Value() interface{} {
return false
case Number:
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:
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:
return true
}
}
type part struct {
@ -518,6 +824,9 @@ proc_val:
// the first double-quote has already been read
s = i
for ; i < len(json); i++ {
if json[i] > '\\' {
continue
}
if json[i] == '"' {
value.Raw = json[s-1 : i+1]
value.Str = json[s:i]
@ -598,14 +907,19 @@ proc_val:
case '}', ']':
if arrch && parts[depth-1].key == "#" {
if alogok {
result := Result{Type: Multi}
var jsons = make([]byte, 0, 64)
jsons = append(jsons, '[')
for j := 0; j < len(alog); j++ {
res := Get(json[alog[j]:], alogkey)
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 {
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 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 {
if t.Type < token.Type {
@ -707,17 +1021,6 @@ func (t Result) Less(token Result, caseSensitive bool) bool {
if t.Type == Number {
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
}

View File

@ -116,32 +116,64 @@ var basicJSON = `{"age":100, "name":{"here":"B\\\"R"},
"firstName": "Elliotte",
"lastName": "Harold",
"email": "cccc"
},
{
"firstName": 1002.3
}
]
}
}`
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
mtok := Get(basicJSON, "loggy.programmers.#.firstName")
if mtok.Type != Multi {
t.Fatal("expected %v, got %v", Multi, mtok.Type)
if token = Parse("-102"); token.Num != -102 {
t.Fatal("expected %v, got %v", -102, token.Num)
}
if len(mtok.Multi) != 3 {
t.Fatalf("expected 3, got %v", len(mtok.Multi))
if token = Parse("102"); token.Num != 102 {
t.Fatal("expected %v, got %v", 102, token.Num)
}
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())
if token = Parse("102.2"); token.Num != 102.2 {
t.Fatal("expected %v, got %v", 102.2, token.Num)
}
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")
if mtok.Type != Multi {
t.Fatal("expected %v, got %v", Multi, mtok.Type)
if mtok.Type != JSON {
t.Fatal("expected %v, got %v", JSON, mtok.Type)
}
if len(mtok.Multi) != 0 {
t.Fatalf("expected 0, got %v", len(mtok.Multi))
if len(mtok.Array()) != 0 {
t.Fatalf("expected 0, got %v", len(mtok.Array()))
}
if Get(basicJSON, "items.3.tags.#").Num != 3 {
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"}` {
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 {
t.Fatal("should be nil")
}
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) {