Skip to content

Commit 9f57509

Browse files
peter-jozsaJózsa Péter
andauthored
Add support for structs (#22)
* Add struct support * Use StructTag.Get() method to not change go version requirements. * Add test cases to test struct support --------- Co-authored-by: Józsa Péter <jozsa.peter@pwstudio.hu>
1 parent 2f8d57c commit 9f57509

2 files changed

Lines changed: 248 additions & 7 deletions

File tree

jsonpath.go

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,8 @@ func get_key(obj interface{}, key string) (interface{}, error) {
344344
if reflect.TypeOf(obj) == nil {
345345
return nil, ErrGetFromNullObj
346346
}
347-
switch reflect.TypeOf(obj).Kind() {
347+
value := reflect.ValueOf(obj)
348+
switch value.Kind() {
348349
case reflect.Map:
349350
// if obj came from stdlib json, its highly likely to be a map[string]interface{}
350351
// in which case we can save having to iterate the map keys to work out if the
@@ -356,17 +357,17 @@ func get_key(obj interface{}, key string) (interface{}, error) {
356357
}
357358
return val, nil
358359
}
359-
for _, kv := range reflect.ValueOf(obj).MapKeys() {
360+
for _, kv := range value.MapKeys() {
360361
//fmt.Println(kv.String())
361362
if kv.String() == key {
362-
return reflect.ValueOf(obj).MapIndex(kv).Interface(), nil
363+
return value.MapIndex(kv).Interface(), nil
363364
}
364365
}
365366
return nil, fmt.Errorf("key error: %s not found in object", key)
366367
case reflect.Slice:
367368
// slice we should get from all objects in it.
368369
res := []interface{}{}
369-
for i := 0; i < reflect.ValueOf(obj).Len(); i++ {
370+
for i := 0; i < value.Len(); i++ {
370371
tmp, _ := get_idx(obj, i)
371372
if key == "" {
372373
res = append(res, tmp)
@@ -377,6 +378,53 @@ func get_key(obj interface{}, key string) (interface{}, error) {
377378
}
378379
}
379380
return res, nil
381+
case reflect.Ptr:
382+
// Unwrap pointer
383+
realValue := value.Elem()
384+
385+
if !realValue.IsValid() {
386+
return nil, fmt.Errorf("null pointer")
387+
}
388+
389+
return get_key(realValue.Interface(), key)
390+
case reflect.Interface:
391+
// Unwrap interface value
392+
realValue := value.Elem()
393+
394+
return get_key(realValue.Interface(), key)
395+
case reflect.Struct:
396+
for i := 0; i < value.NumField(); i++ {
397+
valueField := value.Field(i)
398+
structField := value.Type().Field(i)
399+
400+
// Embeded struct
401+
if valueField.Kind() == reflect.Struct && structField.Anonymous {
402+
v, _ := get_key(valueField.Interface(), key)
403+
if v != nil {
404+
return v, nil
405+
}
406+
} else {
407+
if structField.Name == key {
408+
return valueField.Interface(), nil
409+
}
410+
411+
if tag := structField.Tag.Get("json"); tag != "" {
412+
values := strings.Split(tag, ",")
413+
for _, tagValue := range values {
414+
// In the following cases json tag names should not be checked:
415+
// ",omitempty", "-", "-,"
416+
if (tagValue == "" && len(values) == 2) || tagValue == "-" {
417+
break
418+
}
419+
if tagValue != "omitempty" && tagValue == key {
420+
return valueField.Interface(), nil
421+
}
422+
}
423+
}
424+
}
425+
}
426+
427+
return nil, fmt.Errorf("key error: %s not found in struct", key)
380428
default:
381429
return nil, fmt.Errorf("object is not map")
382430
}

jsonpath_test.go

Lines changed: 196 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,46 @@ import (
1010
"testing"
1111
)
1212

13-
var json_data interface{}
13+
type Data struct {
14+
Store *Store `json:"store"`
15+
Expensive float64 `json:"expensive"`
16+
}
17+
18+
type Store struct {
19+
Book []Goods `json:"book"`
20+
Bicycle []Goods `json:"bicycle"`
21+
}
22+
23+
type Goods interface {
24+
GetPrice() float64
25+
}
26+
27+
type Book struct {
28+
Category string `json:"category,omitempty"`
29+
Author string `json:"author"`
30+
Title string `json:"title"`
31+
Price float64 `json:"price"`
32+
ISBN string `json:"isbn"`
33+
Tags []string `json:"-"`
34+
}
35+
36+
func (b *Book) GetPrice() float64 {
37+
return b.Price
38+
}
39+
40+
type Bicycle struct {
41+
Color string `json:"colour"`
42+
Price float64
43+
}
44+
45+
func (b *Bicycle) GetPrice() float64 {
46+
return b.Price
47+
}
48+
49+
var (
50+
json_data interface{}
51+
structData *Data
52+
)
1453

1554
func init() {
1655
data := `
@@ -53,6 +92,51 @@ func init() {
5392
}
5493
`
5594
json.Unmarshal([]byte(data), &json_data)
95+
96+
structData = &Data{
97+
Store: &Store{
98+
Book: []Goods{
99+
&Book{
100+
Category: "reference",
101+
Author: "Nigel Rees",
102+
Title: "Sayings of the Century",
103+
Price: 8.95,
104+
},
105+
&Book{
106+
Category: "fiction",
107+
Author: "Evelyn Waugh",
108+
Title: "Sword of Honour",
109+
Price: 12.99,
110+
Tags: []string{"fiction", "best-seller", "best-deal"},
111+
},
112+
&Book{
113+
Category: "fiction",
114+
Author: "Herman Melville",
115+
Title: "Moby Dick",
116+
ISBN: "0-553-21311-3",
117+
Price: 8.99,
118+
},
119+
&Book{
120+
Category: "fiction",
121+
Author: "J. R. R. Tolkien",
122+
Title: "The Lord of the Rings",
123+
ISBN: "0-395-19395-8",
124+
Price: 22.99,
125+
},
126+
},
127+
Bicycle: []Goods{
128+
&Bicycle{
129+
Color: "red",
130+
Price: 19.95,
131+
},
132+
&Bicycle{
133+
Color: "brown",
134+
Price: 9.99,
135+
},
136+
},
137+
},
138+
Expensive: 10,
139+
}
56140
}
57141

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

71155
// nagtive single index
@@ -114,6 +198,65 @@ func Test_jsonpath_JsonPathLookup_1(t *testing.T) {
114198
}
115199
}
116200

201+
func Test_jsonpath_JsonPathLookup_structs_1(t *testing.T) {
202+
// key from root
203+
res, _ := JsonPathLookup(structData, "$.expensive")
204+
if res_v, ok := res.(float64); ok != true || res_v != 10.0 {
205+
t.Errorf("expensive should be 10")
206+
}
207+
208+
// single index
209+
res, _ = JsonPathLookup(structData, "$.store.book[0].price")
210+
if res_v, ok := res.(float64); ok != true || res_v != 8.95 {
211+
t.Errorf("$.store.book[0].price should be 8.95, received: %v", res)
212+
}
213+
214+
// nagtive single index
215+
res, _ = JsonPathLookup(structData, "$.store.book[-1].isbn")
216+
if res_v, ok := res.(string); ok != true || res_v != "0-395-19395-8" {
217+
t.Errorf("$.store.book[-1].isbn should be \"0-395-19395-8\", received: %v", res)
218+
}
219+
220+
// multiple index
221+
res, err := JsonPathLookup(structData, "$.store.book[0,1].price")
222+
t.Log(err, res)
223+
if res_v, ok := res.([]interface{}); ok != true || res_v[0].(float64) != 8.95 || res_v[1].(float64) != 12.99 {
224+
t.Errorf("exp: [8.95, 12.99], got: %v", res)
225+
}
226+
227+
// multiple index
228+
res, err = JsonPathLookup(structData, "$.store.book[0,1].title")
229+
t.Log(err, res)
230+
if res_v, ok := res.([]interface{}); ok != true {
231+
if res_v[0].(string) != "Sayings of the Century" || res_v[1].(string) != "Sword of Honour" {
232+
t.Errorf("title are wrong: %v", res)
233+
}
234+
}
235+
236+
// full array
237+
res, err = JsonPathLookup(structData, "$.store.book[0:].price")
238+
t.Log(err, res)
239+
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 {
240+
t.Errorf("exp: [8.95, 12.99, 8.99, 22.99], got: %v", res)
241+
}
242+
243+
// range
244+
res, err = JsonPathLookup(structData, "$.store.book[0:1].price")
245+
t.Log(err, res)
246+
if res_v, ok := res.([]interface{}); ok != true || res_v[0].(float64) != 8.95 || res_v[1].(float64) != 12.99 {
247+
t.Errorf("exp: [8.95, 12.99], got: %v", res)
248+
}
249+
250+
// range
251+
res, err = JsonPathLookup(structData, "$.store.book[0:1].title")
252+
t.Log(err, res)
253+
if res_v, ok := res.([]interface{}); ok != true {
254+
if res_v[0].(string) != "Sayings of the Century" || res_v[1].(string) != "Sword of Honour" {
255+
t.Errorf("title are wrong: %v", res)
256+
}
257+
}
258+
}
259+
117260
func Test_jsonpath_JsonPathLookup_filter(t *testing.T) {
118261
res, err := JsonPathLookup(json_data, "$.store.book[?(@.isbn)].isbn")
119262
t.Log(err, res)
@@ -124,7 +267,7 @@ func Test_jsonpath_JsonPathLookup_filter(t *testing.T) {
124267
}
125268
}
126269

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

287+
func Test_jsonpath_JsonPathLookup_struct_filter(t *testing.T) {
288+
res, err := JsonPathLookup(structData, "$.store.book[?(@.isbn)].ISBN")
289+
t.Log(err, res)
290+
291+
if res_v, ok := res.([]interface{}); ok != true {
292+
if res_v[0].(string) != "0-553-21311-3" || res_v[1].(string) != "0-395-19395-8" {
293+
t.Errorf("error: %v", res)
294+
}
295+
}
296+
}
297+
144298
func Test_jsonpath_authors_of_all_books(t *testing.T) {
145299
query := "store.book[*].author"
146300
expected := []string{
@@ -459,6 +613,45 @@ func Test_jsonpath_get_key(t *testing.T) {
459613
}
460614
}
461615

616+
func Test_jsonpath_get_key_struct(t *testing.T) {
617+
res, err := get_key(structData, "Store")
618+
fmt.Println(err, res)
619+
if err != nil {
620+
t.Errorf("failed to get struct key: %v", err)
621+
return
622+
}
623+
if res_v, ok := res.(*Store); !ok || len(res_v.Bicycle) != 2 || len(res_v.Book) != 4 {
624+
t.Error("get field of struct failed")
625+
return
626+
}
627+
628+
res, err = get_key(structData, "hah")
629+
if err == nil {
630+
t.Error("failed to raise missing key error")
631+
return
632+
}
633+
634+
res, err = get_key(structData, "store")
635+
if err != nil {
636+
t.Errorf("failed to get struct key: %v", err)
637+
return
638+
}
639+
if res_v, ok := res.(*Store); !ok || len(res_v.Bicycle) != 2 || len(res_v.Book) != 4 {
640+
t.Error("get field of struct by json tag name failed")
641+
return
642+
}
643+
644+
res, err = get_key(structData.Store.Book[0], "Category")
645+
if err != nil {
646+
t.Errorf("failed to get field of struct masked by interface: %v", err)
647+
return
648+
}
649+
if res.(string) != "reference" {
650+
t.Errorf("not expected value returned: %v", res)
651+
return
652+
}
653+
}
654+
462655
func Test_jsonpath_get_idx(t *testing.T) {
463656
obj := []interface{}{1, 2, 3, 4}
464657
res, err := get_idx(obj, 0)

0 commit comments

Comments
 (0)