Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for structs #22

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 52 additions & 4 deletions jsonpath.go
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,8 @@ func get_key(obj interface{}, key string) (interface{}, error) {
if reflect.TypeOf(obj) == nil {
return nil, ErrGetFromNullObj
}
switch reflect.TypeOf(obj).Kind() {
value := reflect.ValueOf(obj)
switch value.Kind() {
case reflect.Map:
// if obj came from stdlib json, its highly likely to be a map[string]interface{}
// in which case we can save having to iterate the map keys to work out if the
Expand All @@ -347,23 +348,70 @@ func get_key(obj interface{}, key string) (interface{}, error) {
}
return val, nil
}
for _, kv := range reflect.ValueOf(obj).MapKeys() {
for _, kv := range value.MapKeys() {
//fmt.Println(kv.String())
if kv.String() == key {
return reflect.ValueOf(obj).MapIndex(kv).Interface(), nil
return value.MapIndex(kv).Interface(), nil
}
}
return nil, fmt.Errorf("key error: %s not found in object", key)
case reflect.Slice:
// slice we should get from all objects in it.
res := []interface{}{}
for i := 0; i < reflect.ValueOf(obj).Len(); i++ {
for i := 0; i < value.Len(); i++ {
tmp, _ := get_idx(obj, i)
if v, err := get_key(tmp, key); err == nil {
res = append(res, v)
}
}
return res, nil
case reflect.Ptr:
// Unwrap pointer
realValue := value.Elem()

if !realValue.IsValid() {
return nil, fmt.Errorf("null pointer")
}

return get_key(realValue.Interface(), key)
case reflect.Interface:
// Unwrap interface value
realValue := value.Elem()

return get_key(realValue.Interface(), key)
case reflect.Struct:
for i := 0; i < value.NumField(); i++ {
valueField := value.Field(i)
structField := value.Type().Field(i)

// Embeded struct
if valueField.Kind() == reflect.Struct && structField.Anonymous {
v, _ := get_key(valueField.Interface(), key)
if v != nil {
return v, nil
}
} else {
if structField.Name == key {
return valueField.Interface(), nil
}

if tag := structField.Tag.Get("json"); tag != "" {
values := strings.Split(tag, ",")
for _, tagValue := range values {
// In the following cases json tag names should not be checked:
// ",omitempty", "-", "-,"
if (tagValue == "" && len(values) == 2) || tagValue == "-" {
break
}
if tagValue != "omitempty" && tagValue == key {
return valueField.Interface(), nil
}
}
}
}
}

return nil, fmt.Errorf("key error: %s not found in struct", key)
default:
return nil, fmt.Errorf("object is not map")
}
Expand Down
209 changes: 201 additions & 8 deletions jsonpath_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,46 @@ import (
"testing"
)

var json_data interface{}
type Data struct {
Store *Store `json:"store"`
Expensive float64 `json:"expensive"`
}

type Store struct {
Book []Goods `json:"book"`
Bicycle []Goods `json:"bicycle"`
}

type Goods interface {
GetPrice() float64
}

type Book struct {
Category string `json:"category,omitempty"`
Author string `json:"author"`
Title string `json:"title"`
Price float64 `json:"price"`
ISBN string `json:"isbn"`
Tags []string `json:"-"`
}

func (b *Book) GetPrice() float64 {
return b.Price
}

type Bicycle struct {
Color string `json:"colour"`
Price float64
}

func (b *Bicycle) GetPrice() float64 {
return b.Price
}

var (
json_data interface{}
structData *Data
)

func init() {
data := `
Expand Down Expand Up @@ -53,6 +92,51 @@ func init() {
}
`
json.Unmarshal([]byte(data), &json_data)

structData = &Data{
Store: &Store{
Book: []Goods{
&Book{
Category: "reference",
Author: "Nigel Rees",
Title: "Sayings of the Century",
Price: 8.95,
},
&Book{
Category: "fiction",
Author: "Evelyn Waugh",
Title: "Sword of Honour",
Price: 12.99,
Tags: []string{"fiction", "best-seller", "best-deal"},
},
&Book{
Category: "fiction",
Author: "Herman Melville",
Title: "Moby Dick",
ISBN: "0-553-21311-3",
Price: 8.99,
},
&Book{
Category: "fiction",
Author: "J. R. R. Tolkien",
Title: "The Lord of the Rings",
ISBN: "0-395-19395-8",
Price: 22.99,
},
},
Bicycle: []Goods{
&Bicycle{
Color: "red",
Price: 19.95,
},
&Bicycle{
Color: "brown",
Price: 9.99,
},
},
},
Expensive: 10,
}
}

func Test_jsonpath_JsonPathLookup_1(t *testing.T) {
Expand All @@ -65,7 +149,7 @@ func Test_jsonpath_JsonPathLookup_1(t *testing.T) {
// single index
res, _ = JsonPathLookup(json_data, "$.store.book[0].price")
if res_v, ok := res.(float64); ok != true || res_v != 8.95 {
t.Errorf("$.store.book[0].price should be 8.95")
t.Errorf("$.store.book[0].price should be 8.95, received: %v", res)
}

// nagtive single index
Expand Down Expand Up @@ -96,7 +180,7 @@ func Test_jsonpath_JsonPathLookup_1(t *testing.T) {
if res_v, ok := res.([]interface{}); ok != true || res_v[0].(float64) != 8.95 || res_v[1].(float64) != 12.99 || res_v[2].(float64) != 8.99 || res_v[3].(float64) != 22.99 {
t.Errorf("exp: [8.95, 12.99, 8.99, 22.99], got: %v", res)
}

// range
res, err = JsonPathLookup(json_data, "$.store.book[0:1].price")
t.Log(err, res)
Expand All @@ -114,6 +198,65 @@ func Test_jsonpath_JsonPathLookup_1(t *testing.T) {
}
}

func Test_jsonpath_JsonPathLookup_structs_1(t *testing.T) {
// key from root
res, _ := JsonPathLookup(structData, "$.expensive")
if res_v, ok := res.(float64); ok != true || res_v != 10.0 {
t.Errorf("expensive should be 10")
}

// single index
res, _ = JsonPathLookup(structData, "$.store.book[0].price")
if res_v, ok := res.(float64); ok != true || res_v != 8.95 {
t.Errorf("$.store.book[0].price should be 8.95, received: %v", res)
}

// nagtive single index
res, _ = JsonPathLookup(structData, "$.store.book[-1].isbn")
if res_v, ok := res.(string); ok != true || res_v != "0-395-19395-8" {
t.Errorf("$.store.book[-1].isbn should be \"0-395-19395-8\", received: %v", res)
}

// multiple index
res, err := JsonPathLookup(structData, "$.store.book[0,1].price")
t.Log(err, res)
if res_v, ok := res.([]interface{}); ok != true || res_v[0].(float64) != 8.95 || res_v[1].(float64) != 12.99 {
t.Errorf("exp: [8.95, 12.99], got: %v", res)
}

// multiple index
res, err = JsonPathLookup(structData, "$.store.book[0,1].title")
t.Log(err, res)
if res_v, ok := res.([]interface{}); ok != true {
if res_v[0].(string) != "Sayings of the Century" || res_v[1].(string) != "Sword of Honour" {
t.Errorf("title are wrong: %v", res)
}
}

// full array
res, err = JsonPathLookup(structData, "$.store.book[0:].price")
t.Log(err, res)
if res_v, ok := res.([]interface{}); ok != true || res_v[0].(float64) != 8.95 || res_v[1].(float64) != 12.99 || res_v[2].(float64) != 8.99 || res_v[3].(float64) != 22.99 {
t.Errorf("exp: [8.95, 12.99, 8.99, 22.99], got: %v", res)
}

// range
res, err = JsonPathLookup(structData, "$.store.book[0:1].price")
t.Log(err, res)
if res_v, ok := res.([]interface{}); ok != true || res_v[0].(float64) != 8.95 || res_v[1].(float64) != 12.99 {
t.Errorf("exp: [8.95, 12.99], got: %v", res)
}

// range
res, err = JsonPathLookup(structData, "$.store.book[0:1].title")
t.Log(err, res)
if res_v, ok := res.([]interface{}); ok != true {
if res_v[0].(string) != "Sayings of the Century" || res_v[1].(string) != "Sword of Honour" {
t.Errorf("title are wrong: %v", res)
}
}
}

func Test_jsonpath_JsonPathLookup_filter(t *testing.T) {
res, err := JsonPathLookup(json_data, "$.store.book[?(@.isbn)].isbn")
t.Log(err, res)
Expand All @@ -124,7 +267,7 @@ func Test_jsonpath_JsonPathLookup_filter(t *testing.T) {
}
}

res, err = JsonPathLookup(json_data, "$.store.book[?(@.price > 10)].title")
res, err = JsonPathLookup(json_data, "$.store.book[?(@.price > 10)].Title")
t.Log(err, res)
if res_v, ok := res.([]interface{}); ok != true {
if res_v[0].(string) != "Sword of Honour" || res_v[1].(string) != "The Lord of the Rings" {
Expand All @@ -141,6 +284,17 @@ func Test_jsonpath_JsonPathLookup_filter(t *testing.T) {
t.Log(err, res)
}

func Test_jsonpath_JsonPathLookup_struct_filter(t *testing.T) {
res, err := JsonPathLookup(structData, "$.store.book[?(@.isbn)].ISBN")
t.Log(err, res)

if res_v, ok := res.([]interface{}); ok != true {
if res_v[0].(string) != "0-553-21311-3" || res_v[1].(string) != "0-395-19395-8" {
t.Errorf("error: %v", res)
}
}
}

func Test_jsonpath_authors_of_all_books(t *testing.T) {
query := "store.book[*].author"
expected := []string{
Expand Down Expand Up @@ -456,6 +610,45 @@ func Test_jsonpath_get_key(t *testing.T) {
fmt.Println(err, res)
}

func Test_jsonpath_get_key_struct(t *testing.T) {
res, err := get_key(structData, "Store")
fmt.Println(err, res)
if err != nil {
t.Errorf("failed to get struct key: %v", err)
return
}
if res_v, ok := res.(*Store); !ok || len(res_v.Bicycle) != 2 || len(res_v.Book) != 4 {
t.Error("get field of struct failed")
return
}

res, err = get_key(structData, "hah")
if err == nil {
t.Error("failed to raise missing key error")
return
}

res, err = get_key(structData, "store")
if err != nil {
t.Errorf("failed to get struct key: %v", err)
return
}
if res_v, ok := res.(*Store); !ok || len(res_v.Bicycle) != 2 || len(res_v.Book) != 4 {
t.Error("get field of struct by json tag name failed")
return
}

res, err = get_key(structData.Store.Book[0], "Category")
if err != nil {
t.Errorf("failed to get field of struct masked by interface: %v", err)
return
}
if res.(string) != "reference" {
t.Errorf("not expected value returned: %v", res)
return
}
}

func Test_jsonpath_get_idx(t *testing.T) {
obj := []interface{}{1, 2, 3, 4}
res, err := get_idx(obj, 0)
Expand Down Expand Up @@ -1179,13 +1372,13 @@ func Test_jsonpath_rootnode_is_array_range(t *testing.T) {
t.Logf("idx: %v, v: %v", idx, v)
}
if len(ares) != 2 {
t.Fatal("len is not 2. got: %v", len(ares))
t.Fatalf("len is not 2. got: %v", len(ares))
}
if ares[0].(float64) != 12.34 {
t.Fatal("idx: 0, should be 12.34. got: %v", ares[0])
t.Fatalf("idx: 0, should be 12.34. got: %v", ares[0])
}
if ares[1].(float64) != 13.34 {
t.Fatal("idx: 0, should be 12.34. got: %v", ares[1])
t.Fatalf("idx: 0, should be 12.34. got: %v", ares[1])
}
}

Expand Down Expand Up @@ -1232,7 +1425,7 @@ func Test_jsonpath_rootnode_is_nested_array_range(t *testing.T) {
t.Logf("idx: %v, v: %v", idx, v)
}
if len(ares) != 2 {
t.Fatal("len is not 2. got: %v", len(ares))
t.Fatalf("len is not 2. got: %v", len(ares))
}

//FIXME: `$[:1].[0].test` got wrong result
Expand Down