mirror of https://github.com/tidwall/gjson.git
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:
parent
44a8706c89
commit
cd422a3e10
50
README.md
50
README.md
|
@ -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
373
gjson.go
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue