-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathjsonq.go
176 lines (156 loc) · 3.67 KB
/
jsonq.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
//
// Copyright (c) 2020 Markku Rossi
//
// All rights reserved.
//
package jsonq
import (
"errors"
"fmt"
"reflect"
)
// Context filters JSON object with Select and extracts values with
// Extract.
type Context struct {
selection []interface{}
err error
}
// Ctx creates a new selection context for the argument JSON root
// value.
func Ctx(root interface{}) *Context {
return &Context{
selection: []interface{}{root},
}
}
// Select selects elements from the context.
func (ctx *Context) Select(q string) *Context {
if ctx.err != nil {
return ctx
}
var result []interface{}
for _, sel := range ctx.selection {
value, err := Get(sel, q)
if err != nil {
ctx.err = err
return ctx
}
arr, ok := value.([]interface{})
if ok {
result = append(result, arr...)
} else {
result = append(result, value)
}
}
ctx.selection = result
return ctx
}
// Error describes an invalid argument passed to Extract.
type Error struct {
Type reflect.Type
}
func (e *Error) Error() string {
if e.Type == nil {
return "jsonq: Extract(nil)"
}
if e.Type.Kind() != reflect.Ptr {
return fmt.Sprintf("jsonq: Extract(non-pointer %s)", e.Type)
}
return fmt.Sprintf("jsonq: Extract(nil %s)", e.Type)
}
// Get gets the current selection from the context.
func (ctx *Context) Get() ([]interface{}, error) {
if ctx.err != nil {
return nil, ctx.err
}
return ctx.selection, nil
}
// Extract extracts values from the current selection into the
// argument value object.
func (ctx *Context) Extract(v interface{}) error {
if ctx.err != nil {
return ctx.err
}
return extract(ctx.selection, reflect.ValueOf(v))
}
func extract(selection []interface{}, rv reflect.Value) error {
if rv.Kind() != reflect.Ptr || rv.IsNil() {
return &Error{
Type: rv.Type(),
}
}
if len(selection) == 0 {
return errors.New("jsonq: empty selection")
}
// Check the pointed value's type.
pointed := reflect.Indirect(rv)
switch pointed.Type().Kind() {
case reflect.Ptr:
return fmt.Errorf("jsonq: pointers not implemented yet")
case reflect.Struct:
if len(selection) != 1 {
return errors.New("jsonq: selection matches more than one item")
}
return extractStruct(selection[0], pointed)
case reflect.Slice:
elemType := pointed.Type().Elem()
switch elemType.Kind() {
case reflect.Ptr:
// Support slice of pointers to struct.
p := elemType.Elem()
if p.Kind() != reflect.Struct {
return fmt.Errorf("jsonq: slice with invalid pointer type %s",
p.Kind())
}
for _, sel := range selection {
v := reflect.New(p)
err := extract([]interface{}{sel}, v)
if err != nil {
return err
}
pointed = reflect.Append(pointed, v)
}
reflect.Indirect(rv).Set(pointed)
return nil
case reflect.Struct:
for _, sel := range selection {
v := reflect.New(elemType)
err := extract([]interface{}{sel}, v)
if err != nil {
return err
}
pointed = reflect.Append(pointed, reflect.Indirect(v))
}
reflect.Indirect(rv).Set(pointed)
return nil
default:
return fmt.Errorf("jsonq: unsupport slice element type: %s",
elemType.Kind())
}
default:
return fmt.Errorf("jsonq: pointed: %s", pointed.Type())
}
}
func extractStruct(sel interface{}, value reflect.Value) error {
for i := 0; i < value.NumField(); i++ {
tag := value.Type().Field(i).Tag.Get("jsonq")
if len(tag) == 0 {
continue
}
field := value.Field(i)
switch field.Type().Kind() {
case reflect.String:
val, err := GetString(sel, tag)
if err == ErrorOptionalMissing {
continue
}
if err != nil {
return err
}
field.SetString(val)
default:
return fmt.Errorf("jsonq: field type %s not supported",
field.Type())
}
}
return nil
}